mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
refactor(lib-checkout,lib-crm): replace SelectedCustomerFacade with CrmTabMetadataService
Remove the redundant SelectedCustomerFacade which was just a thin wrapper around CrmTabMetadataService. Update all consumers to use CrmTabMetadataService directly for better consistency and reduced indirection. Changes: - Remove SelectedCustomerFacade and its exports - Update reward catalog components to use SelectedCustomerBonusCardsResource - Replace local resource factories with global resources - Update purchase options modal and customer details components - Simplify reward action component logic and improve button state handling Ref: #5202, #5263, #5358
This commit is contained in:
@@ -5,3 +5,4 @@ export * from './models';
|
||||
export * from './schemas';
|
||||
export * from './services';
|
||||
export * from './store';
|
||||
export * from './resources';
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
export * from './reward-catalog.resource';
|
||||
export * from './reward-customer-card.resource';
|
||||
export * from './reward-shopping-cart.resource';
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import { inject, resource } from '@angular/core';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
import { CustomerCardsFacade } from '@isa/crm/data-access';
|
||||
import { SelectedCustomerFacade } from '@isa/crm/data-access';
|
||||
|
||||
export const createRewardCustomerCardResource = () => {
|
||||
const tabId = injectTabId();
|
||||
const customerCardsFacade = inject(CustomerCardsFacade);
|
||||
const selectedCustomerFacade = inject(SelectedCustomerFacade);
|
||||
return resource({
|
||||
loader: async ({ abortSignal }) => {
|
||||
const customerId = selectedCustomerFacade.get(tabId()!);
|
||||
|
||||
if (!customerId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fetchCustomerCardsResponse = await customerCardsFacade.get(
|
||||
{ customerId },
|
||||
abortSignal,
|
||||
);
|
||||
|
||||
const activePrimaryCard =
|
||||
fetchCustomerCardsResponse.result?.find(
|
||||
(card) => card.isPrimary && card.isActive,
|
||||
) ?? undefined;
|
||||
|
||||
return activePrimaryCard;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { inject, resource } from '@angular/core';
|
||||
import {
|
||||
CheckoutMetadataService,
|
||||
ShoppingCartFacade,
|
||||
} from '@isa/checkout/data-access';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
|
||||
export const createRewardShoppingCartResource = () => {
|
||||
const tabId = injectTabId();
|
||||
const checkoutMetadataService = inject(CheckoutMetadataService);
|
||||
const shoppingCartFacade = inject(ShoppingCartFacade);
|
||||
return resource({
|
||||
loader: async ({ abortSignal }) => {
|
||||
const shoppingCartId = checkoutMetadataService.getRewardShoppingCartId(
|
||||
tabId()!,
|
||||
);
|
||||
|
||||
if (!shoppingCartId) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fetchCustomerCardsResponse =
|
||||
await shoppingCartFacade.getShoppingCart(shoppingCartId, abortSignal);
|
||||
|
||||
return fetchCustomerCardsResponse;
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -4,7 +4,10 @@
|
||||
uiButton
|
||||
color="brand"
|
||||
size="large"
|
||||
[disabled]="!hasSelectedItems()"
|
||||
[disabled]="
|
||||
!hasSelectedItems() ||
|
||||
(!!primaryBonusCard() && primaryBonusCardPoints() <= 0)
|
||||
"
|
||||
(click)="continueToPurchasingOptions()"
|
||||
>
|
||||
Prämie auswählen
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
linkedSignal,
|
||||
inject,
|
||||
computed,
|
||||
} from '@angular/core';
|
||||
import {
|
||||
RewardCatalogStore,
|
||||
@@ -13,9 +13,12 @@ import { injectTabId } from '@isa/core/tabs';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { PurchaseOptionsModalService } from '@modal/purchase-options';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { createRewardCustomerCardResource } from '../resources';
|
||||
import { Router } from '@angular/router';
|
||||
import { getRouteToCustomer } from '../helpers';
|
||||
import {
|
||||
getPrimaryBonusCard,
|
||||
SelectedCustomerBonusCardsResource,
|
||||
} from '@isa/crm/data-access';
|
||||
|
||||
@Component({
|
||||
selector: 'reward-action',
|
||||
@@ -32,16 +35,24 @@ export class RewardActionComponent {
|
||||
#purchasingOptionsModal = inject(PurchaseOptionsModalService);
|
||||
#shoppingCartFacade = inject(ShoppingCartFacade);
|
||||
#checkoutMetadataService = inject(CheckoutMetadataService);
|
||||
#customerBonusCardsResource = inject(SelectedCustomerBonusCardsResource)
|
||||
.resource;
|
||||
|
||||
rewardCustomerCardResource = createRewardCustomerCardResource(); // TODO: Refactor and use global resource
|
||||
readonly customerCardResponseValue =
|
||||
this.#customerBonusCardsResource.value.asReadonly();
|
||||
|
||||
customerCardResponseValue = linkedSignal(() =>
|
||||
this.rewardCustomerCardResource.value(),
|
||||
bonusCards = computed(() => {
|
||||
return this.customerCardResponseValue() ?? [];
|
||||
});
|
||||
|
||||
primaryBonusCard = computed(() => getPrimaryBonusCard(this.bonusCards()));
|
||||
primaryBonusCardPoints = computed(
|
||||
() => this.primaryBonusCard()?.totalPoints ?? 0,
|
||||
);
|
||||
|
||||
selectedItems = linkedSignal(() => this.#store.selectedItems());
|
||||
selectedItems = computed(() => this.#store.selectedItems());
|
||||
|
||||
hasSelectedItems = linkedSignal(() => {
|
||||
hasSelectedItems = computed(() => {
|
||||
return Object.keys(this.selectedItems() || {}).length > 0;
|
||||
});
|
||||
|
||||
@@ -82,10 +93,7 @@ export class RewardActionComponent {
|
||||
}
|
||||
|
||||
async #navigation(tabId: number) {
|
||||
const hasCustomer = this.customerCardResponseValue();
|
||||
if (hasCustomer) {
|
||||
// TODO: Update Reward Shopping Cart Resource
|
||||
} else {
|
||||
if (!this.primaryBonusCard()) {
|
||||
const route = getRouteToCustomer(tabId);
|
||||
await this.#router.navigate(route.path, {
|
||||
queryParams: route.queryParams,
|
||||
|
||||
@@ -18,6 +18,8 @@ import { RewardListComponent } from './reward-list/reward-list.component';
|
||||
import { injectRestoreScrollPosition } from '@isa/utils/scroll-position';
|
||||
import { RewardActionComponent } from './reward-action/reward-action.component';
|
||||
import { TabService } from '@isa/core/tabs';
|
||||
import { SelectedRewardShoppingCartResource } from '@isa/checkout/data-access';
|
||||
import { SelectedCustomerBonusCardsResource } from '@isa/crm/data-access';
|
||||
|
||||
/**
|
||||
* Factory function to retrieve query settings from the activated route data.
|
||||
@@ -33,6 +35,8 @@ function querySettingsFactory() {
|
||||
styleUrl: './reward-catalog.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
SelectedRewardShoppingCartResource,
|
||||
SelectedCustomerBonusCardsResource,
|
||||
provideFilter(
|
||||
withQuerySettingsFactory(querySettingsFactory),
|
||||
withQueryParamsSync(),
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply h-[9.5rem] desktop:h-32 w-full flex flex-row gap-20 rounded-2xl bg-isa-neutral-400 p-6 text-isa-neutral-900;
|
||||
@apply h-[9.5rem] desktop:h-32 w-full flex flex-row justify-between rounded-2xl bg-isa-neutral-400 p-6;
|
||||
}
|
||||
|
||||
@@ -1,43 +1,45 @@
|
||||
<div class="flex flex-col gap-[0.125rem]">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="isa-text-body-1-regular"
|
||||
>{{ card()?.firstName }} {{ card()?.lastName }}
|
||||
</span>
|
||||
<span class="isa-text-body-1-bold"
|
||||
>{{ customerCardTotalPoints() }} Lesepunkte</span
|
||||
<div class="flex flex-row gap-20 text-isa-neutral-900">
|
||||
<div class="flex flex-col gap-[0.125rem]">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="isa-text-body-1-regular"
|
||||
>{{ primaryBonusCard()?.firstName }} {{ primaryBonusCard()?.lastName }}
|
||||
</span>
|
||||
<span class="isa-text-body-1-bold"
|
||||
>{{ primaryBonusCardPoints() }} Lesepunkte</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<ui-text-button
|
||||
class="self-start -ml-[0.6rem]"
|
||||
type="button"
|
||||
color="subtle"
|
||||
size="small"
|
||||
(click)="resetCustomerAndCart()"
|
||||
>
|
||||
Zurücksetzen
|
||||
</ui-text-button>
|
||||
</div>
|
||||
|
||||
<ui-text-button
|
||||
class="self-start -ml-[0.6rem]"
|
||||
type="button"
|
||||
color="subtle"
|
||||
size="small"
|
||||
(click)="reset.emit()"
|
||||
>
|
||||
Zurücksetzen
|
||||
</ui-text-button>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="isa-text-body-1-regular">Prämien ausgewählt</span>
|
||||
<span
|
||||
*uiSkeletonLoader="shoppingCartResponseFetching()"
|
||||
class="isa-text-body-1-bold"
|
||||
>{{ cartItemsLength() }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="isa-text-body-1-regular">Prämien ausgewählt</span>
|
||||
<span
|
||||
*uiSkeletonLoader="shoppingCartResponseFetching()"
|
||||
class="isa-text-body-1-bold"
|
||||
>{{ cartItemsLength() }}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
@if (cartItemsLength()) {
|
||||
<button
|
||||
@if (cartItemsLength() && primaryBonusCardPoints() > 0) {
|
||||
<a
|
||||
class="self-start"
|
||||
data-which="continue-to-reward-checkout"
|
||||
data-what="continue-to-reward-checkout"
|
||||
uiButton
|
||||
color="brand"
|
||||
size="large"
|
||||
[disabled]="disableContinueCta()"
|
||||
(click)="continueToRewardCheckout()"
|
||||
routerLink="./cart"
|
||||
>
|
||||
Prämienausgabe
|
||||
</button>
|
||||
</a>
|
||||
}
|
||||
|
||||
@@ -1,60 +1,68 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
input,
|
||||
linkedSignal,
|
||||
output,
|
||||
computed,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { ButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { BonusCardInfo } from '@isa/crm/data-access';
|
||||
import { createRewardShoppingCartResource } from '../../resources';
|
||||
import {
|
||||
CrmTabMetadataService,
|
||||
getPrimaryBonusCard,
|
||||
SelectedCustomerBonusCardsResource,
|
||||
} from '@isa/crm/data-access';
|
||||
import { SkeletonLoaderDirective } from '@isa/ui/skeleton-loader';
|
||||
import {
|
||||
CheckoutMetadataService,
|
||||
SelectedRewardShoppingCartResource,
|
||||
} from '@isa/checkout/data-access';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
|
||||
@Component({
|
||||
selector: 'reward-customer-card',
|
||||
templateUrl: './reward-customer-card.component.html',
|
||||
styleUrl: './reward-customer-card.component.css',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TextButtonComponent, ButtonComponent, SkeletonLoaderDirective],
|
||||
imports: [
|
||||
RouterLink,
|
||||
TextButtonComponent,
|
||||
ButtonComponent,
|
||||
SkeletonLoaderDirective,
|
||||
],
|
||||
})
|
||||
export class RewardCustomerCardComponent {
|
||||
card = input.required<BonusCardInfo>();
|
||||
reset = output<void>();
|
||||
#crmTabMetadataService = inject(CrmTabMetadataService);
|
||||
#checkoutMetadataService = inject(CheckoutMetadataService);
|
||||
tabId = injectTabId();
|
||||
#shoppingCartResource = inject(SelectedRewardShoppingCartResource).resource;
|
||||
#customerBonusCardsResource = inject(SelectedCustomerBonusCardsResource)
|
||||
.resource;
|
||||
|
||||
rewardShoppingCartResource = createRewardShoppingCartResource(); // TODO: Refactor and use global resource
|
||||
readonly customerCardResponseValue =
|
||||
this.#customerBonusCardsResource.value.asReadonly();
|
||||
|
||||
shoppingCartResponseValue = linkedSignal(() =>
|
||||
this.rewardShoppingCartResource.value(),
|
||||
bonusCards = computed(() => {
|
||||
return this.customerCardResponseValue() ?? [];
|
||||
});
|
||||
|
||||
primaryBonusCard = computed(() => getPrimaryBonusCard(this.bonusCards()));
|
||||
primaryBonusCardPoints = computed(
|
||||
() => this.primaryBonusCard()?.totalPoints ?? 0,
|
||||
);
|
||||
|
||||
shoppingCartResponseFetching = linkedSignal(
|
||||
() => this.rewardShoppingCartResource.status() === 'loading',
|
||||
);
|
||||
readonly shoppingCartResponseValue =
|
||||
this.#shoppingCartResource.value.asReadonly();
|
||||
readonly shoppingCartResponseFetching = this.#shoppingCartResource.isLoading;
|
||||
|
||||
cartItemsLength = linkedSignal(
|
||||
cartItemsLength = computed(
|
||||
() => this.shoppingCartResponseValue()?.items?.length ?? 0,
|
||||
);
|
||||
|
||||
cartTotalLoyaltyPoints = linkedSignal(() => {
|
||||
return (
|
||||
this.shoppingCartResponseValue()?.items?.reduce(
|
||||
(sum, item) => sum + (item?.data?.loyalty?.value ?? 0),
|
||||
0,
|
||||
) ?? 0
|
||||
resetCustomerAndCart() {
|
||||
this.#crmTabMetadataService.setSelectedCustomerId(this.tabId()!, undefined);
|
||||
this.#checkoutMetadataService.setRewardShoppingCartId(
|
||||
this.tabId()!,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
customerCardTotalPoints = linkedSignal(() => this.card()?.totalPoints ?? 0);
|
||||
|
||||
disableContinueCta = linkedSignal(() => {
|
||||
return (
|
||||
this.shoppingCartResponseFetching() ||
|
||||
this.customerCardTotalPoints() <= 0 ||
|
||||
this.cartTotalLoyaltyPoints() > this.customerCardTotalPoints()
|
||||
);
|
||||
});
|
||||
|
||||
continueToRewardCheckout() {
|
||||
// TODO: Navigate to Reward Checkout Page
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
@let card = customerCardResponseValue();
|
||||
@let card = primaryBonusCard();
|
||||
@let cardFetching = customerCardResponseFetching();
|
||||
@if (!cardFetching) {
|
||||
@if (card) {
|
||||
<reward-customer-card
|
||||
[card]="card"
|
||||
(reset)="resetCustomer()"
|
||||
></reward-customer-card>
|
||||
<reward-customer-card></reward-customer-card>
|
||||
} @else {
|
||||
<reward-start-card></reward-start-card>
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
linkedSignal,
|
||||
} from '@angular/core';
|
||||
import { RewardStartCardComponent } from './reward-start-card/reward-start-card.component';
|
||||
import { RewardCustomerCardComponent } from './reward-customer-card/reward-customer-card.component';
|
||||
import { createRewardCustomerCardResource } from '../resources';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
import { SelectedCustomerFacade } from '@isa/crm/data-access';
|
||||
import {
|
||||
getPrimaryBonusCard,
|
||||
SelectedCustomerBonusCardsResource,
|
||||
} from '@isa/crm/data-access';
|
||||
@Component({
|
||||
selector: 'reward-header',
|
||||
templateUrl: './reward-header.component.html',
|
||||
@@ -22,18 +23,17 @@ import { SelectedCustomerFacade } from '@isa/crm/data-access';
|
||||
],
|
||||
})
|
||||
export class RewardHeaderComponent {
|
||||
tabId = injectTabId();
|
||||
selectedCustomerFacade = inject(SelectedCustomerFacade);
|
||||
|
||||
rewardCustomerCardResource = createRewardCustomerCardResource(); // TODO: Refactor and use global resource
|
||||
#customerBonusCardsResource = inject(SelectedCustomerBonusCardsResource)
|
||||
.resource;
|
||||
|
||||
readonly customerCardResponseValue =
|
||||
this.rewardCustomerCardResource.value.asReadonly();
|
||||
this.#customerBonusCardsResource.value.asReadonly();
|
||||
readonly customerCardResponseFetching =
|
||||
this.rewardCustomerCardResource.isLoading;
|
||||
this.#customerBonusCardsResource.isLoading;
|
||||
|
||||
resetCustomer() {
|
||||
this.selectedCustomerFacade.clear(this.tabId()!);
|
||||
this.rewardCustomerCardResource.reload();
|
||||
}
|
||||
bonusCards = computed(() => {
|
||||
return this.customerCardResponseValue() ?? [];
|
||||
});
|
||||
|
||||
primaryBonusCard = computed(() => getPrimaryBonusCard(this.bonusCards()));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from './customer-cards.facade';
|
||||
export * from './customer.facade';
|
||||
export * from './selected-customer-id.facade';
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { CrmTabMetadataService } from '../services';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SelectedCustomerFacade {
|
||||
#crmTabMetadataService = inject(CrmTabMetadataService);
|
||||
|
||||
set(tabId: number, customerId: number) {
|
||||
this.#crmTabMetadataService.setSelectedCustomerId(tabId, customerId);
|
||||
}
|
||||
|
||||
get(tabId: number) {
|
||||
return this.#crmTabMetadataService.selectedCustomerId(tabId);
|
||||
}
|
||||
|
||||
clear(tabId: number) {
|
||||
this.#crmTabMetadataService.setSelectedCustomerId(tabId, undefined);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user