Merged PR 2055: feature(ui-label, ahf, warenausgabe, customer-orders): Added and Updated Labe...

feature(ui-label, ahf, warenausgabe, customer-orders): Added and Updated Label Library and Label to the Views, Updated Positioning

Ref: #5479
This commit is contained in:
Nino Righi
2025-11-28 12:37:11 +00:00
committed by Lorenz Hilpert
parent a5bb8b2895
commit 41630d5d7c
47 changed files with 1830 additions and 1549 deletions

View File

@@ -1,29 +1,66 @@
@if (orderItem$ | async; as orderItem) {
<div #features class="page-customer-order-details-item__features">
@if (orderItem?.features?.prebooked) {
<img [uiOverlayTrigger]="prebookedTooltip" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
<img
[uiOverlayTrigger]="prebookedTooltip"
src="/assets/images/tag_icon_preorder.svg"
[alt]="orderItem?.features?.prebooked"
/>
<ui-tooltip
yPosition="above"
xPosition="after"
[yOffset]="-11"
[xOffset]="-8"
#prebookedTooltip
[closeable]="true"
>
Artikel wird für Sie vorgemerkt.
</ui-tooltip>
}
@if (notificationsSent$ | async; as notificationsSent) {
@if (notificationsSent?.NOTIFICATION_EMAIL) {
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
<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 />
@for (notification of notificationsSent?.NOTIFICATION_EMAIL; track notification) {
@for (
notification of notificationsSent?.NOTIFICATION_EMAIL;
track notification
) {
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr
<br />
}
</ui-tooltip>
}
@if (notificationsSent?.NOTIFICATION_SMS) {
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
<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 />
@for (notification of notificationsSent?.NOTIFICATION_SMS; track notification) {
@for (
notification of notificationsSent?.NOTIFICATION_SMS;
track notification
) {
{{ notification | date: 'dd.MM.yyyy | HH:mm' }} Uhr
<br />
}
@@ -33,7 +70,10 @@
</div>
<div class="page-customer-order-details-item__item-container">
<div class="page-customer-order-details-item__thumbnail">
<img [src]="orderItem.product?.ean | productImage" [alt]="orderItem.product?.name" />
<img
[src]="orderItem.product?.ean | productImage"
[alt]="orderItem.product?.name"
/>
</div>
<div class="page-customer-order-details-item__details">
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
@@ -42,19 +82,29 @@
#elementDistance="uiElementDistance"
[style.max-width.px]="elementDistance.distanceChange | async"
class="flex flex-col"
>
<div class="font-normal mb-[0.375rem]">{{ orderItem.product?.contributors }}</div>
>
@if (hasRewardPoints$ | async) {
<ui-label class="w-10 mb-2">Prämie</ui-label>
}
<div class="font-normal mb-[0.375rem]">
{{ orderItem.product?.contributors }}
</div>
<div>{{ orderItem.product?.name }}</div>
</h3>
<div class="history-wrapper flex flex-col items-end justify-center">
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">Historie</button>
<button
class="cta-history text-p1"
(click)="historyClick.emit(orderItem)"
>
Historie
</button>
@if (selectable$ | async) {
<input
[ngModel]="selected$ | async"
(ngModelChange)="setSelected($event)"
class="isa-select-bullet mt-4"
type="checkbox"
/>
/>
}
</div>
</div>
@@ -72,19 +122,26 @@
[showSpinner]="false"
></ui-quantity-dropdown>
}
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
<span class="overall-quantity"
>(von {{ orderItem?.overallQuantity }})</span
>
</div>
</div>
@if (!!orderItem.product?.formatDetail) {
<div class="detail">
<div class="label">Format</div>
<div class="value">
@if (orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN') {
@if (
orderItem?.product?.format &&
orderItem?.product?.format !== 'UNKNOWN'
) {
<img
class="format-icon"
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
[src]="
'/assets/images/Icon_' + orderItem.product?.format + '.svg'
"
alt="format icon"
/>
/>
}
<span>{{ orderItem.product?.formatDetail }}</span>
</div>
@@ -96,10 +153,17 @@
<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) {
@@ -133,14 +197,23 @@
orderItemFeature(orderItem) === 'Versand' ||
orderItemFeature(orderItem) === 'B2B-Versand' ||
orderItemFeature(orderItem) === 'DIG-Versand'
) {
{{ orderItem?.estimatedDelivery ? 'Lieferung zwischen' : 'Lieferung ab' }}
) {
{{
orderItem?.estimatedDelivery
? 'Lieferung zwischen'
: 'Lieferung ab'
}}
}
@if (orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage') {
@if (
orderItemFeature(orderItem) === 'Abholung' ||
orderItemFeature(orderItem) === 'Rücklage'
) {
Abholung ab
}
</div>
@if (!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate) {
@if (
!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate
) {
<div class="value bg-[#D8DFE5] rounded w-max px-2">
@if (!!orderItem?.estimatedDelivery) {
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
@@ -155,14 +228,22 @@
</div>
@if (getOrderItemTrackingData(orderItem); as trackingData) {
<div class="page-customer-order-details-item__tracking-details">
<div class="label">{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}</div>
<div class="label">
{{ trackingData.length > 1 ? 'Sendungsnummern' : 'Sendungsnummer' }}
</div>
@for (tracking of trackingData; track tracking) {
@if (tracking.trackingProvider === 'DHL' && !isNative) {
<a class="value text-[#0556B4]" [href]="getTrackingNumberLink(tracking.trackingNumber)" target="_blank">
<a
class="value text-[#0556B4]"
[href]="getTrackingNumberLink(tracking.trackingNumber)"
target="_blank"
>
{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}
</a>
} @else {
<p class="value">{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}</p>
<p class="value">
{{ tracking.trackingProvider }}: {{ tracking.trackingNumber }}
</p>
}
}
</div>
@@ -206,7 +287,9 @@
@if (!!receipt?.printedDate) {
<div class="detail">
<div class="label">Erstellt am</div>
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
<div class="value">
{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr
</div>
</div>
}
@if (!!receipt?.receiptText) {
@@ -219,12 +302,20 @@
<div class="detail">
<div class="label">Belegart</div>
<div class="value">
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
{{
receipt?.receiptType === 1
? 'Lieferschein'
: receipt?.receiptType === 64
? 'Zahlungsbeleg'
: '-'
}}
</div>
</div>
}
}
<div class="page-customer-order-details-item__comment flex flex-col items-start mt-[1.625rem]">
<div
class="page-customer-order-details-item__comment flex flex-col items-start mt-[1.625rem]"
>
<div class="label mb-[0.375rem]">Anmerkung</div>
<div class="flex flex-row w-full">
<textarea
@@ -248,17 +339,23 @@
<button
type="reset"
class="clear"
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
>
(click)="
specialCommentControl.setValue('');
saveSpecialComment();
triggerResize()
"
>
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
}
@if (specialCommentControl?.enabled && specialCommentControl.dirty) {
@if (
specialCommentControl?.enabled && specialCommentControl.dirty
) {
<button
class="cta-save"
type="submit"
(click)="saveSpecialComment()"
>
>
Speichern
</button>
}

View File

@@ -15,12 +15,25 @@ import { DomainOmsService, DomainReceiptService } from '@domain/oms';
import { ComponentStore } from '@ngrx/component-store';
import { tapResponse } from '@ngrx/operators';
import { OrderDTO, OrderItemListItemDTO, ReceiptDTO, ReceiptType } from '@generated/swagger/oms-api';
import {
OrderDTO,
OrderItemListItemDTO,
ReceiptDTO,
ReceiptType,
} from '@generated/swagger/oms-api';
import { isEqual } from 'lodash';
import { combineLatest, NEVER, Subject, Observable } from 'rxjs';
import { catchError, filter, first, map, switchMap, withLatestFrom } from 'rxjs/operators';
import {
catchError,
filter,
first,
map,
switchMap,
withLatestFrom,
} from 'rxjs/operators';
import { CustomerOrderDetailsStore } from '../customer-order-details.store';
import { EnvironmentService } from '@core/environment';
import { getOrderItemRewardFeature } from '@isa/oms/data-access';
export interface CustomerOrderDetailsItemComponentState {
orderItem?: OrderItemListItemDTO;
@@ -59,7 +72,12 @@ export class CustomerOrderDetailsItemComponent
// Remove Prev OrderItem from selected list
this._store.selectOrderItem(this.orderItem, false);
this.patchState({ orderItem, quantity: orderItem?.quantity, receipts: [], more: false });
this.patchState({
orderItem,
quantity: orderItem?.quantity,
receipts: [],
more: false,
});
this.specialCommentControl.reset(orderItem?.specialComment);
// Add New OrderItem to selected list if selected was set to true by its input
@@ -94,8 +112,23 @@ export class CustomerOrderDetailsItemComponent
),
);
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
hasRewardPoints$ = this.orderItem$.pipe(
map((orderItem) => getOrderItemRewardFeature(orderItem) !== undefined),
);
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,
),
);
get quantity() {
@@ -111,7 +144,9 @@ export class CustomerOrderDetailsItemComponent
@Input()
get selected() {
return this._store.selectedeOrderItemSubsetIds.includes(this.orderItem?.orderItemSubsetId);
return this._store.selectedeOrderItemSubsetIds.includes(
this.orderItem?.orderItemSubsetId,
);
}
set selected(selected: boolean) {
if (this.selected !== selected) {
@@ -120,22 +155,36 @@ export class CustomerOrderDetailsItemComponent
}
}
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedeOrderItemSubsetIds$]).pipe(
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId)),
readonly selected$ = combineLatest([
this.orderItem$,
this._store.selectedeOrderItemSubsetIds$,
]).pipe(
map(([orderItem, selectedItems]) =>
selectedItems.includes(orderItem?.orderItemSubsetId),
),
);
@Output()
selectedChange = new EventEmitter<boolean>();
get selectable() {
return this._store.itemsSelectable && this._store.items.length > 1 && this._store.fetchPartial;
return (
this._store.itemsSelectable &&
this._store.items.length > 1 &&
this._store.fetchPartial
);
}
readonly selectable$ = combineLatest([
this._store.items$,
this._store.itemsSelectable$,
this._store.fetchPartial$,
]).pipe(map(([orderItems, selectable, fetchPartial]) => orderItems.length > 1 && selectable && fetchPartial));
]).pipe(
map(
([orderItems, selectable, fetchPartial]) =>
orderItems.length > 1 && selectable && fetchPartial,
),
);
get receipts() {
return this.get((s) => s.receipts);
@@ -173,6 +222,7 @@ export class CustomerOrderDetailsItemComponent
});
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
ngOnInit() {}
ngOnDestroy() {
@@ -182,37 +232,39 @@ export class CustomerOrderDetailsItemComponent
this._onDestroy$.complete();
}
loadReceipts = this.effect((done$: Observable<(receipts: ReceiptDTO[]) => void | undefined>) =>
done$.pipe(
withLatestFrom(this.orderItem$),
filter(([_, orderItem]) => !!orderItem),
switchMap(([done, orderItem]) =>
this._domainReceiptService
.getReceipts({
receiptType: 65 as ReceiptType,
ids: [orderItem.orderItemSubsetId],
eagerLoading: 1,
})
.pipe(
tapResponse(
(res) => {
const receipts = res.result.map((r) => r.item3?.data).filter((f) => !!f);
this.receipts = receipts;
loadReceipts = this.effect(
(done$: Observable<(receipts: ReceiptDTO[]) => void | undefined>) =>
done$.pipe(
withLatestFrom(this.orderItem$),
filter(([, orderItem]) => !!orderItem),
switchMap(([done, orderItem]) =>
this._domainReceiptService
.getReceipts({
receiptType: 65 as ReceiptType,
ids: [orderItem.orderItemSubsetId],
eagerLoading: 1,
})
.pipe(
tapResponse(
(res) => {
const receipts = res.result
.map((r) => r.item3?.data)
.filter((f) => !!f);
this.receipts = receipts;
if (typeof done === 'function') {
done?.(receipts);
}
},
(err) => {
if (typeof done === 'function') {
done?.([]);
}
},
() => {},
if (typeof done === 'function') {
done?.(receipts);
}
},
() => {
if (typeof done === 'function') {
done?.([]);
}
},
),
),
),
),
),
),
);
async saveSpecialComment() {
@@ -220,7 +272,7 @@ export class CustomerOrderDetailsItemComponent
try {
this.specialCommentControl.reset(this.specialCommentControl.value);
const res = await this._omsService
await this._omsService
.patchComment({
orderId,
orderItemId,
@@ -230,7 +282,10 @@ export class CustomerOrderDetailsItemComponent
.pipe(first())
.toPromise();
this.orderItem = { ...this.orderItem, specialComment: this.specialCommentControl.value ?? '' };
this.orderItem = {
...this.orderItem,
specialComment: this.specialCommentControl.value ?? '',
};
this._store.updateOrderItems([this.orderItem]);
this.specialCommentChanged.emit();
} catch (error) {
@@ -253,8 +308,9 @@ export class CustomerOrderDetailsItemComponent
orderItemFeature(orderItemListItem: OrderItemListItemDTO) {
const orderItems = this.order?.items;
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features
?.orderType;
return orderItems?.find(
(orderItem) => orderItem.data.id === orderItemListItem.orderItemId,
)?.data?.features?.orderType;
}
getOrderItemTrackingData(
@@ -263,15 +319,18 @@ export class CustomerOrderDetailsItemComponent
const orderItems = this.order?.items;
const completeTrackingInformation = orderItems
?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)
?.data?.subsetItems?.find((subsetItem) => subsetItem.id === orderItemListItem.orderItemSubsetId)
?.data?.trackingNumber;
?.data?.subsetItems?.find(
(subsetItem) => subsetItem.id === orderItemListItem.orderItemSubsetId,
)?.data?.trackingNumber;
if (!completeTrackingInformation) {
return;
}
// Beispielnummer: 'DHL: 124124' - Bei mehreren Tracking-Informationen muss noch ein Splitter eingebaut werden, je nach dem welcher Trenner verwendet wird
const trackingInformationPairs = completeTrackingInformation.split(':').map((obj) => obj.trim());
const trackingInformationPairs = completeTrackingInformation
.split(':')
.map((obj) => obj.trim());
return this._trackingTransformationHelper(trackingInformationPairs);
}
@@ -282,7 +341,10 @@ export class CustomerOrderDetailsItemComponent
return trackingInformationPairs.reduce(
(acc, current, index, array) => {
if (index % 2 === 0) {
acc.push({ trackingProvider: current, trackingNumber: array[index + 1] });
acc.push({
trackingProvider: current,
trackingNumber: array[index + 1],
});
}
return acc;
},

View File

@@ -17,6 +17,7 @@ import { ProductImageModule } from '@cdn/product-image';
import { CustomerOrderDetailsStore } from './customer-order-details.store';
import { UiDatepickerModule } from '@ui/datepicker';
import { UiDropdownModule } from '@ui/dropdown';
import { LabelComponent } from '@isa/ui/label';
@NgModule({
imports: [
@@ -34,6 +35,7 @@ import { UiDropdownModule } from '@ui/dropdown';
CustomerOrderPipesModule,
ProductImageModule,
CustomerOrderDetailsTagsComponent,
LabelComponent,
],
exports: [
CustomerOrderDetailsComponent,
@@ -41,7 +43,11 @@ import { UiDropdownModule } from '@ui/dropdown';
CustomerOrderDetailsHeaderComponent,
CustomerOrderDetailsTagsComponent,
],
declarations: [CustomerOrderDetailsComponent, CustomerOrderDetailsItemComponent, CustomerOrderDetailsHeaderComponent],
declarations: [
CustomerOrderDetailsComponent,
CustomerOrderDetailsItemComponent,
CustomerOrderDetailsHeaderComponent,
],
providers: [CustomerOrderDetailsStore],
})
export class CustomerOrderDetailsModule {}

View File

@@ -11,13 +11,11 @@
[alt]="name"
/>
}
@if (hasRewardPoints$ | async) {
<ui-label [type]="Labeltype.Tag" [priority]="LabelPriority.High">
Prämie
</ui-label>
}
</div>
<div class="grid grid-flow-row gap-2">
@if (hasRewardPoints$ | async) {
<ui-label class="w-10">Prämie</ui-label>
}
<div class="grid grid-flow-col justify-between items-end">
<span>{{ orderItem.product?.contributors }}</span>
@if (orderDetailsHistoryRoute$ | async; as orderDetailsHistoryRoute) {
@@ -25,7 +23,7 @@
[routerLink]="orderDetailsHistoryRoute.path"
[queryParams]="orderDetailsHistoryRoute.urlTree.queryParams"
[queryParamsHandling]="'merge'"
class="text-brand font-bold text-xl"
class="text-brand font-bold text-xl relative -top-8"
>
Historie
</a>
@@ -64,7 +62,9 @@
<div class="col-data">
@if (hasRewardPoints$ | async) {
<div class="col-label">Prämie</div>
<div class="col-value">{{ rewardPoints$ | async | number: '1.0-0' }} Lesepunkte</div>
<div class="col-value">
{{ rewardPoints$ | async | number: '1.0-0' }} Lesepunkte
</div>
} @else {
<div class="col-label">Preis</div>
<div class="col-value">

View File

@@ -21,7 +21,7 @@ import { map, takeUntil } from 'rxjs/operators';
import { CustomerSearchStore } from '../../store';
import { CustomerSearchNavigation } from '@shared/services/navigation';
import { PaymentTypePipe } from '@shared/pipes/customer';
import { LabelComponent, Labeltype, LabelPriority } from '@isa/ui/label';
import { LabelComponent } from '@isa/ui/label';
import { getOrderItemRewardFeature } from '@isa/oms/data-access';
import { IconComponent } from '@shared/components/icon';
@@ -113,9 +113,6 @@ export class CustomerOrderItemListItemComponent implements OnInit, OnDestroy {
map((orderItem) => getOrderItemRewardFeature(orderItem)),
);
Labeltype = Labeltype;
LabelPriority = LabelPriority;
ngOnInit() {
this.customerId$
.pipe(takeUntil(this._onDestroy))

View File

@@ -1,31 +1,75 @@
@if (orderItem) {
<div #features class="page-pickup-shelf-details-item__features">
@if (orderItem?.features?.prebooked) {
<img [uiOverlayTrigger]="prebookedTooltip" src="/assets/images/tag_icon_preorder.svg" [alt]="orderItem?.features?.prebooked" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #prebookedTooltip [closeable]="true">
<img
[uiOverlayTrigger]="prebookedTooltip"
src="/assets/images/tag_icon_preorder.svg"
[alt]="orderItem?.features?.prebooked"
/>
<ui-tooltip
yPosition="above"
xPosition="after"
[yOffset]="-11"
[xOffset]="-8"
#prebookedTooltip
[closeable]="true"
>
Artikel wird für Sie vorgemerkt.
</ui-tooltip>
}
@if (hasEmailNotification$ | async) {
<img [uiOverlayTrigger]="emailTooltip" src="/assets/images/email_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #emailTooltip [closeable]="true">
<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 />
@for (notifications of emailNotificationDates$ | async; track notifications) {
@for (notificationDate of notifications.dates; track notificationDate) {
{{ notifications.type | notificationType }} {{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr
@for (
notifications of emailNotificationDates$ | async;
track notifications
) {
@for (
notificationDate of notifications.dates;
track notificationDate
) {
{{ notifications.type | notificationType }}
{{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr
<br />
}
}
</ui-tooltip>
}
@if (hasSmsNotification$ | async) {
<img [uiOverlayTrigger]="smsTooltip" src="/assets/images/sms_bookmark.svg" />
<ui-tooltip yPosition="above" xPosition="after" [yOffset]="-11" [xOffset]="-8" #smsTooltip [closeable]="true">
<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 />
@for (notifications of smsNotificationDates$ | async; track notifications) {
@for (notificationDate of notifications.dates; track notificationDate) {
@for (
notifications of smsNotificationDates$ | async;
track notifications
) {
@for (
notificationDate of notifications.dates;
track notificationDate
) {
{{ notificationDate | date: 'dd.MM.yyyy | HH:mm' }} Uhr
<br />
}
@@ -39,280 +83,336 @@
[productImageNavigation]="orderItem?.product?.ean"
[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]">
<h3
[uiElementDistance]="features"
#elementDistance="uiElementDistance"
[style.max-width.px]="elementDistance.distanceChange | async"
class="flex flex-col"
>
@if (hasRewardPoints$ | async) {
<ui-label class="w-10 mb-2">Prämie</ui-label>
}
<div class="font-normal mb-[0.375rem]">
{{ orderItem.product?.contributors }}
</div>
<div>{{ orderItem.product?.name }}</div>
</h3>
<div class="history-wrapper flex flex-col items-end justify-center">
<button
class="cta-history text-p1"
(click)="historyClick.emit(orderItem)"
>
Historie
</button>
@if (selectable$ | async) {
<input
[ngModel]="selected$ | async"
(ngModelChange)="
setSelected($event);
tracker.trackEvent({
category: 'pickup-shelf-list-item',
action: 'select',
name: orderItem?.product?.name,
value: $event ? 1 : 0,
})
"
class="isa-select-bullet mt-4"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
}
</div>
</div>
<div class="page-pickup-shelf-details-item__details">
<div class="flex flex-row justify-between items-start mb-[1.3125rem]">
<h3
[uiElementDistance]="features"
#elementDistance="uiElementDistance"
[style.max-width.px]="elementDistance.distanceChange | async"
class="flex flex-col"
>
<div class="font-normal mb-[0.375rem]">{{ orderItem.product?.contributors }}</div>
<div>{{ orderItem.product?.name }}</div>
</h3>
<div class="history-wrapper flex flex-col items-end justify-center">
<button class="cta-history text-p1" (click)="historyClick.emit(orderItem)">Historie</button>
@if (selectable$ | async) {
<input
[ngModel]="selected$ | async"
(ngModelChange)="
setSelected($event);
tracker.trackEvent({
category: 'pickup-shelf-list-item',
action: 'select',
name: orderItem?.product?.name,
value: $event ? 1 : 0,
})
"
class="isa-select-bullet mt-4"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
}
</div>
<div class="detail">
<div class="label">Menge</div>
<div class="value">
@if (!(canChangeQuantity$ | async)) {
{{ orderItem?.quantity }}x
}
@if (canChangeQuantity$ | async) {
<ui-quantity-dropdown
[showTrash]="false"
[range]="orderItem?.quantity"
[(ngModel)]="quantity"
(ngModelChange)="
tracker.trackEvent({
category: 'pickup-shelf-list-item',
action: 'quantity',
name: orderItem?.product?.name,
value: $event,
})
"
[showSpinner]="false"
matomoTracker
#tracker="matomo"
></ui-quantity-dropdown>
}
<span class="overall-quantity"
>(von {{ orderItem?.overallQuantity }})</span
>
</div>
</div>
@if (!!orderItem.product?.formatDetail) {
<div class="detail">
<div class="label">Menge</div>
<div class="label">Format</div>
<div class="value">
@if (!(canChangeQuantity$ | async)) {
{{ orderItem?.quantity }}x
@if (
orderItem?.product?.format &&
orderItem?.product?.format !== 'UNKNOWN'
) {
<img
class="format-icon"
[src]="
'/assets/images/Icon_' + orderItem.product?.format + '.svg'
"
alt="format icon"
/>
}
@if (canChangeQuantity$ | async) {
<ui-quantity-dropdown
[showTrash]="false"
[range]="orderItem?.quantity"
[(ngModel)]="quantity"
(ngModelChange)="
tracker.trackEvent({ category: 'pickup-shelf-list-item', action: 'quantity', name: orderItem?.product?.name, value: $event })
"
[showSpinner]="false"
matomoTracker
#tracker="matomo"
></ui-quantity-dropdown>
}
<span class="overall-quantity">(von {{ orderItem?.overallQuantity }})</span>
<span>{{ orderItem.product?.formatDetail }}</span>
</div>
</div>
@if (!!orderItem.product?.formatDetail) {
<div class="detail">
<div class="label">Format</div>
}
@if (!!orderItem.product?.ean) {
<div class="detail">
<div class="label">ISBN/EAN</div>
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
}
@if (orderItem.price !== undefined || (hasRewardPoints$ | async)) {
<div class="detail">
@if (hasRewardPoints$ | async) {
<div class="label">Prämie</div>
<div class="value">
@if (orderItem?.product?.format && orderItem?.product?.format !== 'UNKNOWN') {
<img
class="format-icon"
[src]="'/assets/images/Icon_' + orderItem.product?.format + '.svg'"
alt="format icon"
/>
}
<span>{{ orderItem.product?.formatDetail }}</span>
</div>
</div>
}
@if (!!orderItem.product?.ean) {
<div class="detail">
<div class="label">ISBN/EAN</div>
<div class="value">{{ orderItem.product?.ean }}</div>
</div>
}
@if (orderItem.price !== undefined || (hasRewardPoints$ | async)) {
<div class="detail">
@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) {
<div class="detail">
<div class="label">MwSt</div>
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
</div>
}
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (orderItem.supplier) {
<div class="detail">
<div class="label">Lieferant</div>
<div class="value">{{ orderItem.supplier }}</div>
@if (!expanded) {
<button
(click)="expanded = !expanded"
type="button"
class="page-pickup-shelf-details-item__more text-[#0556B4] font-bold flex flex-row items-center justify-center"
[class.flex-row-reverse]="!expanded"
>
<shared-icon class="mr-1" icon="arrow-back" [size]="20" [class.ml-1]="!expanded" [class.rotate-180]="!expanded"></shared-icon>
{{ expanded ? 'Weniger' : 'Mehr' }}
</button>
}
</div>
}
@if (!!orderItem.ssc || !!orderItem.sscText) {
<div class="detail">
<div class="label">Meldenummer</div>
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
</div>
}
@if (expanded) {
@if (!!orderItem.targetBranch) {
<div class="detail">
<div class="label">Zielfiliale</div>
<div class="value">{{ orderItem.targetBranch }}</div>
{{ rewardPoints$ | async | number: '1.0-0' }} Lesepunkte
</div>
} @else {
<div class="label">Preis</div>
<div class="value">{{ orderItem.price | currency: 'EUR' }}</div>
}
<div class="detail">
<div class="label">
@if (
orderItemFeature(orderItem) === 'Versand' ||
orderItemFeature(orderItem) === 'B2B-Versand' ||
orderItemFeature(orderItem) === 'DIG-Versand'
) {
{{ orderItem?.estimatedDelivery ? 'Lieferung zwischen' : 'Lieferung ab' }}
}
@if (orderItemFeature(orderItem) === 'Abholung' || orderItemFeature(orderItem) === 'Rücklage') {
Abholung ab
}
</div>
@if (!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate) {
<div class="value bg-[#D8DFE5] rounded w-max px-2">
@if (!!orderItem?.estimatedDelivery) {
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
{{ orderItem?.estimatedDelivery?.stop | date: 'dd.MM.yy' }}
} @else {
@if (!!orderItem?.estimatedShippingDate) {
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
}
}
</div>
}
</div>
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (!!orderItem?.compartmentCode) {
<div class="detail">
<div class="label">Abholfachnr.</div>
<div class="value">{{ orderItem?.compartmentCode }}</div>
</div>
}
<div class="detail">
<div class="label">Vormerker</div>
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
</div>
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (!!orderItem.paymentProcessing) {
<div class="detail">
<div class="label">Zahlungsweg</div>
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
</div>
}
@if (!!orderItem.paymentType) {
<div class="detail">
<div class="label">Zahlungsart</div>
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
</div>
}
@if (receiptCount$ | async; as count) {
<h4 class="receipt-header">
{{ count > 1 ? 'Belege' : 'Beleg' }}
</h4>
}
@for (receipt of receipts$ | async; track receipt) {
@if (!!receipt?.receiptNumber) {
<div class="detail">
<div class="label">Belegnummer</div>
<div class="value">{{ receipt?.receiptNumber }}</div>
</div>
}
@if (!!receipt?.printedDate) {
<div class="detail">
<div class="label">Erstellt am</div>
<div class="value">{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr</div>
</div>
}
@if (!!receipt?.receiptText) {
<div class="detail">
<div class="label">Rechnungstext</div>
<div class="value">{{ receipt?.receiptText || '-' }}</div>
</div>
}
@if (!!receipt?.receiptType) {
<div class="detail">
<div class="label">Belegart</div>
<div class="value">
{{ receipt?.receiptType === 1 ? 'Lieferschein' : receipt?.receiptType === 64 ? 'Zahlungsbeleg' : '-' }}
</div>
</div>
}
}
@if (!!orderItem.paymentProcessing || !!orderItem.paymentType || !!(receiptCount$ | async)) {
<hr
class="border-[#EDEFF0] border-t-2 my-4"
/>
}
@if (expanded) {
</div>
}
@if (!!orderItem.retailPrice?.vat?.inPercent) {
<div class="detail">
<div class="label">MwSt</div>
<div class="value">{{ orderItem.retailPrice?.vat?.inPercent }}%</div>
</div>
}
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (orderItem.supplier) {
<div class="detail">
<div class="label">Lieferant</div>
<div class="value">{{ orderItem.supplier }}</div>
@if (!expanded) {
<button
(click)="expanded = !expanded"
type="button"
class="page-pickup-shelf-details-item__less text-[#0556B4] font-bold flex flex-row items-center justify-center"
>
<shared-icon class="mr-1" icon="arrow-back" [size]="20"></shared-icon>
Weniger
class="page-pickup-shelf-details-item__more text-[#0556B4] font-bold flex flex-row items-center justify-center"
[class.flex-row-reverse]="!expanded"
>
<shared-icon
class="mr-1"
icon="arrow-back"
[size]="20"
[class.ml-1]="!expanded"
[class.rotate-180]="!expanded"
></shared-icon>
{{ expanded ? 'Weniger' : 'Mehr' }}
</button>
}
</div>
}
@if (!!orderItem.ssc || !!orderItem.sscText) {
<div class="detail">
<div class="label">Meldenummer</div>
<div class="value">{{ orderItem.ssc }} - {{ orderItem.sscText }}</div>
</div>
}
@if (expanded) {
@if (!!orderItem.targetBranch) {
<div class="detail">
<div class="label">Zielfiliale</div>
<div class="value">{{ orderItem.targetBranch }}</div>
</div>
}
<div class="page-pickup-shelf-details-item__comment flex flex-col items-start mt-[1.625rem]">
<div class="label mb-[0.375rem]">Anmerkung</div>
<div class="flex flex-row w-full">
<textarea
matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
maxlength="200"
#specialCommentInput
(keydown.delete)="triggerResize()"
(keydown.backspace)="triggerResize()"
type="text"
name="comment"
placeholder="Eine Anmerkung hinzufügen"
[formControl]="specialCommentControl"
[class.inactive]="!specialCommentControl.dirty"
></textarea>
<div class="comment-actions">
@if (!!specialCommentControl.value?.length) {
<button
type="reset"
class="clear"
(click)="specialCommentControl.setValue(''); saveSpecialComment(); triggerResize()"
>
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
}
@if (specialCommentControl?.enabled && specialCommentControl.dirty) {
<button
class="cta-save"
type="submit"
(click)="saveSpecialComment()"
matomoClickCategory="pickup-shelf-details-item"
matomoClickAction="save"
matomoClickName="special-comment"
>
Speichern
</button>
<div class="detail">
<div class="label">
@if (
orderItemFeature(orderItem) === 'Versand' ||
orderItemFeature(orderItem) === 'B2B-Versand' ||
orderItemFeature(orderItem) === 'DIG-Versand'
) {
{{
orderItem?.estimatedDelivery
? 'Lieferung zwischen'
: 'Lieferung ab'
}}
}
@if (
orderItemFeature(orderItem) === 'Abholung' ||
orderItemFeature(orderItem) === 'Rücklage'
) {
Abholung ab
}
</div>
@if (
!!orderItem?.estimatedDelivery || !!orderItem?.estimatedShippingDate
) {
<div class="value bg-[#D8DFE5] rounded w-max px-2">
@if (!!orderItem?.estimatedDelivery) {
{{ orderItem?.estimatedDelivery?.start | date: 'dd.MM.yy' }} und
{{ orderItem?.estimatedDelivery?.stop | date: 'dd.MM.yy' }}
} @else {
@if (!!orderItem?.estimatedShippingDate) {
{{ orderItem?.estimatedShippingDate | date: 'dd.MM.yy' }}
}
}
</div>
}
</div>
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (!!orderItem?.compartmentCode) {
<div class="detail">
<div class="label">Abholfachnr.</div>
<div class="value">{{ orderItem?.compartmentCode }}</div>
</div>
}
<div class="detail">
<div class="label">Vormerker</div>
<div class="value">{{ orderItem.isPrebooked ? 'Ja' : 'Nein' }}</div>
</div>
<hr class="border-[#EDEFF0] border-t-2 my-4" />
@if (!!orderItem.paymentProcessing) {
<div class="detail">
<div class="label">Zahlungsweg</div>
<div class="value">{{ orderItem.paymentProcessing || '-' }}</div>
</div>
}
@if (!!orderItem.paymentType) {
<div class="detail">
<div class="label">Zahlungsart</div>
<div class="value">{{ orderItem.paymentType | paymentType }}</div>
</div>
}
@if (receiptCount$ | async; as count) {
<h4 class="receipt-header">
{{ count > 1 ? 'Belege' : 'Beleg' }}
</h4>
}
@for (receipt of receipts$ | async; track receipt) {
@if (!!receipt?.receiptNumber) {
<div class="detail">
<div class="label">Belegnummer</div>
<div class="value">{{ receipt?.receiptNumber }}</div>
</div>
}
@if (!!receipt?.printedDate) {
<div class="detail">
<div class="label">Erstellt am</div>
<div class="value">
{{ receipt?.printedDate | date: 'dd.MM.yy | HH:mm' }} Uhr
</div>
</div>
}
@if (!!receipt?.receiptText) {
<div class="detail">
<div class="label">Rechnungstext</div>
<div class="value">{{ receipt?.receiptText || '-' }}</div>
</div>
}
@if (!!receipt?.receiptType) {
<div class="detail">
<div class="label">Belegart</div>
<div class="value">
{{
receipt?.receiptType === 1
? 'Lieferschein'
: receipt?.receiptType === 64
? 'Zahlungsbeleg'
: '-'
}}
</div>
</div>
}
}
@if (
!!orderItem.paymentProcessing ||
!!orderItem.paymentType ||
!!(receiptCount$ | async)
) {
<hr class="border-[#EDEFF0] border-t-2 my-4" />
}
@if (expanded) {
<button
(click)="expanded = !expanded"
type="button"
class="page-pickup-shelf-details-item__less text-[#0556B4] font-bold flex flex-row items-center justify-center"
>
<shared-icon
class="mr-1"
icon="arrow-back"
[size]="20"
></shared-icon>
Weniger
</button>
}
}
<div
class="page-pickup-shelf-details-item__comment flex flex-col items-start mt-[1.625rem]"
>
<div class="label mb-[0.375rem]">Anmerkung</div>
<div class="flex flex-row w-full">
<textarea
matInput
cdkTextareaAutosize
#autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1"
cdkAutosizeMaxRows="5"
maxlength="200"
#specialCommentInput
(keydown.delete)="triggerResize()"
(keydown.backspace)="triggerResize()"
type="text"
name="comment"
placeholder="Eine Anmerkung hinzufügen"
[formControl]="specialCommentControl"
[class.inactive]="!specialCommentControl.dirty"
></textarea>
<div class="comment-actions">
@if (!!specialCommentControl.value?.length) {
<button
type="reset"
class="clear"
(click)="
specialCommentControl.setValue('');
saveSpecialComment();
triggerResize()
"
>
<shared-icon icon="close" [size]="24"></shared-icon>
</button>
}
@if (
specialCommentControl?.enabled && specialCommentControl.dirty
) {
<button
class="cta-save"
type="submit"
(click)="saveSpecialComment()"
matomoClickCategory="pickup-shelf-details-item"
matomoClickAction="save"
matomoClickName="special-comment"
>
Speichern
</button>
}
</div>
</div>
</div>
</div>
}
</div>
}

View File

@@ -1,5 +1,10 @@
import { CdkTextareaAutosize, TextFieldModule } from '@angular/cdk/text-field';
import { AsyncPipe, CurrencyPipe, DatePipe, DecimalPipe } from '@angular/common';
import {
AsyncPipe,
CurrencyPipe,
DatePipe,
DecimalPipe,
} from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
@@ -11,12 +16,23 @@ import {
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 {
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 { LabelComponent } from '@isa/ui/label';
import { UiTooltipModule } from '@ui/tooltip';
import { PickupShelfPaymentTypePipe } from '../pipes/payment-type.pipe';
import { IconModule } from '@shared/components/icon';
@@ -60,8 +76,8 @@ export interface PickUpShelfDetailsItemComponentState {
NotificationTypePipe,
NavigateOnClickDirective,
MatomoModule,
LabelComponent
],
LabelComponent,
],
})
export class PickUpShelfDetailsItemComponent
extends ComponentStore<PickUpShelfDetailsItemComponentState>
@@ -88,7 +104,11 @@ export class PickUpShelfDetailsItemComponent
this._store.selectOrderItem(this.orderItem, false);
}
this.patchState({ orderItem, quantity: orderItem?.quantity, more: false });
this.patchState({
orderItem,
quantity: orderItem?.quantity,
more: false,
});
this.specialCommentControl.reset(orderItem?.specialComment);
// Add New OrderItem to selected list if selected was set to true by its input
if (this.get((s) => s.selected)) {
@@ -110,16 +130,24 @@ export class PickUpShelfDetailsItemComponent
readonly orderItem$ = this.select((s) => s.orderItem);
emailNotificationDates$ = this.orderItem$.pipe(
switchMap((orderItem) => this._store.getEmailNotificationDate$(orderItem?.orderItemSubsetId)),
switchMap((orderItem) =>
this._store.getEmailNotificationDate$(orderItem?.orderItemSubsetId),
),
);
hasEmailNotification$ = this.emailNotificationDates$.pipe(map((dates) => dates?.length > 0));
hasEmailNotification$ = this.emailNotificationDates$.pipe(
map((dates) => dates?.length > 0),
);
smsNotificationDates$ = this.orderItem$.pipe(
switchMap((orderItem) => this._store.getSmsNotificationDate$(orderItem?.orderItemSubsetId)),
switchMap((orderItem) =>
this._store.getSmsNotificationDate$(orderItem?.orderItemSubsetId),
),
);
hasSmsNotification$ = this.smsNotificationDates$.pipe(map((dates) => dates?.length > 0));
hasSmsNotification$ = this.smsNotificationDates$.pipe(
map((dates) => dates?.length > 0),
);
/**
* Observable that indicates whether the order item has reward points (Lesepunkte).
@@ -137,8 +165,15 @@ export class PickUpShelfDetailsItemComponent
map((orderItem) => getOrderItemRewardFeature(orderItem)),
);
canChangeQuantity$ = combineLatest([this.orderItem$, this._store.fetchPartial$]).pipe(
map(([item, partialPickup]) => ([16, 8192].includes(item?.processingStatus) || partialPickup) && item.quantity > 1),
canChangeQuantity$ = combineLatest([
this.orderItem$,
this._store.fetchPartial$,
]).pipe(
map(
([item, partialPickup]) =>
([16, 8192].includes(item?.processingStatus) || partialPickup) &&
item.quantity > 1,
),
);
get quantity() {
@@ -147,7 +182,10 @@ export class PickUpShelfDetailsItemComponent
set quantity(quantity: number) {
if (this.quantity !== quantity) {
this.patchState({ quantity });
this._store.setSelectedOrderItemQuantity({ orderItemSubsetId: this.orderItem.orderItemSubsetId, quantity });
this._store.setSelectedOrderItemQuantity({
orderItemSubsetId: this.orderItem.orderItemSubsetId,
quantity,
});
}
}
@@ -155,7 +193,9 @@ export class PickUpShelfDetailsItemComponent
@Input()
get selected() {
return this._store.selectedOrderItemIds.includes(this.orderItem?.orderItemSubsetId);
return this._store.selectedOrderItemIds.includes(
this.orderItem?.orderItemSubsetId,
);
}
set selected(selected: boolean) {
if (this.selected !== selected) {
@@ -164,23 +204,40 @@ export class PickUpShelfDetailsItemComponent
}
}
readonly selected$ = combineLatest([this.orderItem$, this._store.selectedOrderItemIds$]).pipe(
map(([orderItem, selectedItems]) => selectedItems.includes(orderItem?.orderItemSubsetId)),
readonly selected$ = combineLatest([
this.orderItem$,
this._store.selectedOrderItemIds$,
]).pipe(
map(([orderItem, selectedItems]) =>
selectedItems.includes(orderItem?.orderItemSubsetId),
),
);
@Output()
selectedChange = new EventEmitter<boolean>();
get isItemSelectable() {
return this._store.orderItems?.some((item) => !!item?.actions && item?.actions?.length > 0);
return this._store.orderItems?.some(
(item) => !!item?.actions && item?.actions?.length > 0,
);
}
get selectable() {
return this.isItemSelectable && this._store.orderItems.length > 1 && this._store.fetchPartial;
return (
this.isItemSelectable &&
this._store.orderItems.length > 1 &&
this._store.fetchPartial
);
}
readonly selectable$ = combineLatest([this._store.orderItems$, this._store.fetchPartial$]).pipe(
map(([orderItems, fetchPartial]) => orderItems.length > 1 && this.isItemSelectable && fetchPartial),
readonly selectable$ = combineLatest([
this._store.orderItems$,
this._store.fetchPartial$,
]).pipe(
map(
([orderItems, fetchPartial]) =>
orderItems.length > 1 && this.isItemSelectable && fetchPartial,
),
);
get receipts() {
@@ -193,7 +250,9 @@ export class PickUpShelfDetailsItemComponent
readonly receipts$ = this._store.receipts$;
readonly receiptCount$ = this.receipts$.pipe(map((receipts) => receipts?.length));
readonly receiptCount$ = this.receipts$.pipe(
map((receipts) => receipts?.length),
);
specialCommentControl = new UntypedFormControl();
@@ -203,10 +262,6 @@ export class PickUpShelfDetailsItemComponent
expanded = false;
// Expose to template
Labeltype = Labeltype;
LabelPriority = LabelPriority;
constructor(private _cdr: ChangeDetectorRef) {
super({
more: false,
@@ -239,8 +294,9 @@ export class PickUpShelfDetailsItemComponent
orderItemFeature(orderItemListItem: DBHOrderItemListItemDTO) {
const orderItems = this.order?.items;
return orderItems?.find((orderItem) => orderItem.data.id === orderItemListItem.orderItemId)?.data?.features
?.orderType;
return orderItems?.find(
(orderItem) => orderItem.data.id === orderItemListItem.orderItemId,
)?.data?.features?.orderType;
}
triggerResize() {

View File

@@ -4,12 +4,16 @@
[routerLinkActive]="!isTablet && !primaryOutletActive ? 'active' : ''"
queryParamsHandling="preserve"
(click)="onDetailsClick()"
>
>
<div
class="page-pickup-shelf-list-item__item-grid-container"
[class.page-pickup-shelf-list-item__item-grid-container-main]="primaryOutletActive"
[class.page-pickup-shelf-list-item__item-grid-container-secondary]="primaryOutletActive && isItemSelectable === undefined"
>
[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">
@if (item?.product?.ean | productImage; as productImage) {
<img
@@ -18,88 +22,126 @@
[productImageNavigation]="item?.product?.ean"
[src]="productImage"
[alt]="item?.product?.name"
/>
}
@if (hasRewardPoints) {
<ui-label [type]="Labeltype.Tag" [priority]="LabelPriority.High">
Prämie
</ui-label>
/>
}
</div>
<div
class="page-pickup-shelf-list-item__item-title-contributors flex flex-col"
[class.mr-32]="showCompartmentCode && item.features?.paid && (isTablet || isDesktopSmall || primaryOutletActive)"
>
[class.mr-32]="
showCompartmentCode &&
item.features?.paid &&
(isTablet || isDesktopSmall || primaryOutletActive)
"
>
@if (hasRewardPoints) {
<ui-label class="w-10 mb-2">Prämie</ui-label>
}
<div
class="page-pickup-shelf-list-item__item-contributors text-p2 font-normal text-ellipsis overflow-hidden max-w-[24rem] whitespace-nowrap mb-[0.375rem]"
>
@for (contributor of contributors; track contributor; let last = $last) {
>
@for (
contributor of contributors;
track contributor;
let last = $last
) {
{{ contributor }}{{ last ? '' : ';' }}
}
</div>
<div
class="page-pickup-shelf-list-item__item-title font-bold text-p1 desktop-small:text-p2"
[class.page-pickup-shelf-list-item__item-title-bigger-text-size]="!primaryOutletActive"
>
[class.page-pickup-shelf-list-item__item-title-bigger-text-size]="
!primaryOutletActive
"
>
{{ item?.product?.name }}
</div>
</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]" [attr.data-ean]="item?.product?.ean">
<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]"
[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]" [attr.data-menge]="item.quantity">
<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" [attr.data-geaendert]="item?.processingStatusDate">
<div
class="page-pickup-shelf-list-item__item-changed text-p2"
[attr.data-geaendert]="item?.processingStatusDate"
>
@if (showChangeDate) {
<div 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 class="font-bold">
{{ item?.processingStatusDate | date: 'dd.MM.yy | HH:mm' }} Uhr
</div>
</div>
} @else {
<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 class="font-bold">
{{ item?.orderDate | date: 'dd.MM.yy | HH:mm' }} Uhr
</div>
</div>
}
</div>
</div>
<div class="page-pickup-shelf-list-item__item-order-number-processing-status-paid flex flex-col">
<div
class="page-pickup-shelf-list-item__item-order-number-processing-status-paid flex flex-col"
>
@if (showCompartmentCode) {
<div
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 }}
>
{{ item?.compartmentCode
}}{{ item?.compartmentInfo && '_' + item?.compartmentInfo }}
</div>
}
<div class="page-pickup-shelf-list-item__item-processing-paid-status flex flex-col font-bold self-end text-p2 mb-[0.375rem]">
<div
class="page-pickup-shelf-list-item__item-processing-paid-status flex flex-col font-bold self-end text-p2 mb-[0.375rem]"
>
<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>
<div class="page-pickup-shelf-list-item__item-paid self-end flex flex-row">
@if (item.features?.paid && (isTablet || isDesktopSmall || primaryOutletActive)) {
<div
class="page-pickup-shelf-list-item__item-paid self-end flex flex-row"
>
@if (
item.features?.paid &&
(isTablet || isDesktopSmall || primaryOutletActive)
) {
<div
class="font-bold flex flex-row items-center justify-center text-p2 text-[#26830C]"
[attr.data-paid]="item.features?.paid"
>
<shared-icon class="flex items-center justify-center mr-[0.375rem]" [size]="24" icon="credit-card"></shared-icon>
>
<shared-icon
class="flex items-center justify-center mr-[0.375rem]"
[size]="24"
icon="credit-card"
></shared-icon>
{{ item.features?.paid }}
</div>
}
@@ -110,28 +152,35 @@
@if (isItemSelectable) {
<div
class="page-pickup-shelf-list-item__item-select-bullet justify-self-end self-center mb-2"
[class.page-pickup-shelf-list-item__item-select-bullet-primary]="primaryOutletActive"
>
[class.page-pickup-shelf-list-item__item-select-bullet-primary]="
primaryOutletActive
"
>
<input
(click)="$event.stopPropagation()"
[ngModel]="selectedItem"
(ngModelChange)="
setSelected();
tracker.trackEvent({ category: 'pickup-shelf-list-item', action: 'select', name: item?.product?.name, value: $event ? 1 : 0 })
"
(ngModelChange)="
setSelected();
tracker.trackEvent({
category: 'pickup-shelf-list-item',
action: 'select',
name: item?.product?.name,
value: $event ? 1 : 0,
})
"
class="isa-select-bullet"
type="checkbox"
matomoTracker
#tracker="matomo"
/>
</div>
}
<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
[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>
</a>
</div>
</a>

View File

@@ -1,12 +1,21 @@
import { DatePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, ElementRef, Input, inject } from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
inject,
} from '@angular/core';
import { RouterLink, RouterLinkActive } from '@angular/router';
import { NavigateOnClickDirective, ProductImageModule } from '@cdn/product-image';
import {
NavigateOnClickDirective,
ProductImageModule,
} from '@cdn/product-image';
import { EnvironmentService } from '@core/environment';
import { IconModule } from '@shared/components/icon';
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
import { getOrderItemRewardFeature } from '@isa/oms/data-access';
import { LabelComponent, Labeltype, LabelPriority } from '@isa/ui/label';
import { LabelComponent } from '@isa/ui/label';
import { UiCommonModule } from '@ui/common';
import { PickupShelfProcessingStatusPipe } from '../pipes/processing-status.pipe';
import { FormsModule } from '@angular/forms';
@@ -32,8 +41,8 @@ import { MatomoModule } from 'ngx-matomo-client';
PickupShelfProcessingStatusPipe,
NavigateOnClickDirective,
MatomoModule,
LabelComponent
],
LabelComponent,
],
providers: [PickupShelfProcessingStatusPipe],
})
export class PickUpShelfListItemComponent {
@@ -45,6 +54,7 @@ export class PickUpShelfListItemComponent {
@Input() primaryOutletActive = false;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@Input() itemDetailsLink: any[] = [];
@Input() isItemSelectable?: boolean = undefined;
@@ -64,7 +74,9 @@ export class PickUpShelfListItemComponent {
}
get contributors() {
return this.item?.product?.contributors?.split(';').map((val) => val.trim());
return this.item?.product?.contributors
?.split(';')
.map((val) => val.trim());
}
get showChangeDate() {
@@ -73,11 +85,19 @@ export class PickUpShelfListItemComponent {
// Zeige nur CompartmentCode an wenn verfügbar
get showCompartmentCode() {
return !!this.item?.compartmentCode && (this.isTablet || this.isDesktopSmall || this.primaryOutletActive);
return (
!!this.item?.compartmentCode &&
(this.isTablet || this.isDesktopSmall || this.primaryOutletActive)
);
}
get processingStatusColor() {
return { 'background-color': this._processingStatusPipe.transform(this.item?.processingStatus, true) };
return {
'background-color': this._processingStatusPipe.transform(
this.item?.processingStatus,
true,
),
};
}
/**
@@ -90,14 +110,12 @@ export class PickUpShelfListItemComponent {
selected$ = this.store.selectedListItems$.pipe(
map((selectedListItems) =>
selectedListItems?.find((item) => item?.orderItemSubsetId === this.item?.orderItemSubsetId),
selectedListItems?.find(
(item) => item?.orderItemSubsetId === this.item?.orderItemSubsetId,
),
),
);
// Expose to template
Labeltype = Labeltype;
LabelPriority = LabelPriority;
constructor(
private _elRef: ElementRef,
private _environment: EnvironmentService,
@@ -125,7 +143,9 @@ export class PickUpShelfListItemComponent {
store: 'PickupShelfDetailsStore',
})) ?? [];
return items.some((i) => i.orderItemSubsetId === this.item.orderItemSubsetId);
return items.some(
(i) => i.orderItemSubsetId === this.item.orderItemSubsetId,
);
}
addOrderItemIntoCache() {
@@ -144,7 +164,10 @@ export class PickUpShelfListItemComponent {
}
scrollIntoView() {
this._elRef.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
this._elRef.nativeElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
setSelected() {

View File

@@ -20,6 +20,7 @@
@import "../../../libs/ui/skeleton-loader/src/skeleton-loader.scss";
@import "../../../libs/ui/tooltip/src/tooltip.scss";
@import "../../../libs/ui/label/src/label.scss";
@import "../../../libs/ui/notice/src/notice.scss";
@import "../../../libs/ui/switch/src/switch.scss";
.input-control {

View File

@@ -1,33 +1,25 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { Labeltype, LabelPriority, LabelComponent } from '@isa/ui/label';
import { LabelComponent } from '@isa/ui/label';
type UiLabelInputs = {
type: Labeltype;
priority: LabelPriority;
type LabelInputs = {
active: boolean;
};
const meta: Meta<UiLabelInputs> = {
const meta: Meta<LabelInputs> = {
component: LabelComponent,
title: 'ui/label/Label',
argTypes: {
type: {
control: { type: 'select' },
options: Object.values(Labeltype),
description: 'Determines the label type',
},
priority: {
control: { type: 'select' },
options: Object.values(LabelPriority),
description: 'Determines the label priority',
active: {
control: { type: 'boolean' },
description: 'Determines if the label is active (hover/pressed state)',
},
},
args: {
type: 'tag',
priority: 'high',
active: false,
},
render: (args) => ({
props: args,
template: `<ui-label ${argsToTemplate(args)}>Prio 1</ui-label>`,
template: `<ui-label ${argsToTemplate(args)}>Prämie</ui-label>`,
}),
};
export default meta;
@@ -37,3 +29,21 @@ type Story = StoryObj<LabelComponent>;
export const Default: Story = {
args: {},
};
export const Active: Story = {
args: {
active: true,
},
};
export const RewardExample: Story = {
args: {},
render: () => ({
template: `
<div style="display: flex; gap: 1rem; align-items: center;">
<ui-label>Prämie</ui-label>
<ui-label [active]="true">Active</ui-label>
</div>
`,
}),
};

View File

@@ -0,0 +1,59 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { PrioLabelComponent } from '@isa/ui/label';
type PrioLabelInputs = {
priority: 1 | 2;
};
const meta: Meta<PrioLabelInputs> = {
component: PrioLabelComponent,
title: 'ui/label/PrioLabel',
argTypes: {
priority: {
control: { type: 'select' },
options: [1, 2],
description: 'The priority level (1 = high, 2 = low)',
},
},
args: {
priority: 1,
},
render: (args) => ({
props: args,
template: `<ui-prio-label ${argsToTemplate(args)}>Pflicht</ui-prio-label>`,
}),
};
export default meta;
type Story = StoryObj<PrioLabelComponent>;
export const Priority1: Story = {
args: {
priority: 1,
},
render: (args) => ({
props: args,
template: `<ui-prio-label [priority]="priority">Pflicht</ui-prio-label>`,
}),
};
export const Priority2: Story = {
args: {
priority: 2,
},
render: (args) => ({
props: args,
template: `<ui-prio-label [priority]="priority">Prio 2</ui-prio-label>`,
}),
};
export const AllPriorities: Story = {
render: () => ({
template: `
<div style="display: flex; gap: 1rem; align-items: center;">
<ui-prio-label [priority]="1">Pflicht</ui-prio-label>
<ui-prio-label [priority]="2">Prio 2</ui-prio-label>
</div>
`,
}),
};

View File

@@ -0,0 +1,70 @@
import { argsToTemplate, type Meta, type StoryObj } from '@storybook/angular';
import { NoticeComponent, NoticePriority } from '@isa/ui/notice';
type NoticeInputs = {
priority: NoticePriority;
};
const meta: Meta<NoticeInputs> = {
component: NoticeComponent,
title: 'ui/notice/Notice',
argTypes: {
priority: {
control: { type: 'select' },
options: Object.values(NoticePriority),
description: 'The priority level (high, medium, low)',
},
},
args: {
priority: NoticePriority.High,
},
render: (args) => ({
props: args,
template: `<ui-notice ${argsToTemplate(args)}>Important message</ui-notice>`,
}),
};
export default meta;
type Story = StoryObj<NoticeComponent>;
export const High: Story = {
args: {
priority: NoticePriority.High,
},
render: (args) => ({
props: args,
template: `<ui-notice [priority]="priority">Action Required</ui-notice>`,
}),
};
export const Medium: Story = {
args: {
priority: NoticePriority.Medium,
},
render: (args) => ({
props: args,
template: `<ui-notice [priority]="priority">Secondary Information</ui-notice>`,
}),
};
export const Low: Story = {
args: {
priority: NoticePriority.Low,
},
render: (args) => ({
props: args,
template: `<ui-notice [priority]="priority">Info Message</ui-notice>`,
}),
};
export const AllPriorities: Story = {
render: () => ({
template: `
<div style="display: flex; flex-direction: column; gap: 1rem;">
<ui-notice priority="high">High Priority</ui-notice>
<ui-notice priority="medium">Medium Priority</ui-notice>
<ui-notice priority="low">Low Priority</ui-notice>
</div>
`,
}),
};