diff --git a/apps/isa-app/src/page/customer/customer-search/details-main-view/details-main-view.component.html b/apps/isa-app/src/page/customer/customer-search/details-main-view/details-main-view.component.html index df8f50769..d30b56974 100644 --- a/apps/isa-app/src/page/customer/customer-search/details-main-view/details-main-view.component.html +++ b/apps/isa-app/src/page/customer/customer-search/details-main-view/details-main-view.component.html @@ -202,7 +202,7 @@ } @else { diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-action/reward-action.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-action/reward-action.component.ts new file mode 100644 index 000000000..8685a8083 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-action/reward-action.component.ts @@ -0,0 +1,29 @@ +import { + ChangeDetectionStrategy, + Component, + linkedSignal, + inject, +} from '@angular/core'; +import { RewardCatalogStore } from '@isa/checkout/data-access'; +import { ButtonComponent } from '@isa/ui/buttons'; + +@Component({ + selector: 'reward-action', + templateUrl: './reward-action.component.html', + styleUrl: './reward-action.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ButtonComponent], +}) +export class RewardActionComponent { + #store = inject(RewardCatalogStore); + + selectedItems = linkedSignal(() => this.#store.selectedItems()); + + hasSelectedItems = linkedSignal(() => { + return Object.keys(this.selectedItems() || {}).length > 0; + }); + + continueToPurchasingOptions() { + console.log('Kaufoptionen Modal öffnen mit: ', this.selectedItems()); + } +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog-item/reward-catalog-item.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-catalog-item/reward-catalog-item.component.ts deleted file mode 100644 index 64be22913..000000000 --- a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog-item/reward-catalog-item.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; - -@Component({ - selector: 'reward-catalog-item', - templateUrl: './reward-catalog-item.component.html', - styleUrl: './reward-catalog-item.component.css', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class RewardCatalogItemComponent {} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.html b/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.html index 7c8d90fd0..204c6106d 100644 --- a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.html +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.html @@ -1,69 +1,7 @@ - - - - {{ hits() }} Einträge - - -
- - - -
- - + + diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.ts index 15e053762..85c55dcc9 100644 --- a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.ts +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-catalog.component.ts @@ -1,7 +1,6 @@ import { ChangeDetectionStrategy, Component, - computed, inject, signal, } from '@angular/core'; @@ -14,13 +13,10 @@ import { SearchTrigger, FilterService, } from '@isa/shared/filter'; -import { IconButtonComponent } from '@isa/ui/buttons'; -import { injectRestoreScrollPosition } from '@isa/utils/scroll-position'; -import { logger } from '@isa/core/logging'; -import { createRewardCatalogResource } from './resources'; -import { EmptyStateComponent } from '@isa/ui/empty-state'; -import { RewardCatalogItemComponent } from './reward-catalog-item/reward-catalog-item.component'; import { RewardHeaderComponent } from './reward-header/reward-header.component'; +import { RewardListComponent } from './reward-list/reward-list.component'; +import { injectRestoreScrollPosition } from '@isa/utils/scroll-position'; +import { RewardActionComponent } from './reward-action/reward-action.component'; /** * Factory function to retrieve query settings from the activated route data. @@ -43,10 +39,9 @@ function querySettingsFactory() { ], imports: [ FilterControlsPanelComponent, - IconButtonComponent, - EmptyStateComponent, RewardHeaderComponent, - RewardCatalogItemComponent, + RewardListComponent, + RewardActionComponent, ], host: { '[class]': @@ -54,78 +49,14 @@ function querySettingsFactory() { }, }) export class RewardCatalogComponent { - /** - * Signal to trigger searches in the reward catalog. - * Can be 'reload', 'initial', or a specific SearchTrigger value. - */ - searchTrigger = signal('initial'); - - /** - * Logger instance for logging component events and errors. - * @private - */ - #logger = logger(() => ({ - component: 'RewardCatalogComponent', - })); - - /** - * FilterService instance for managing filter state and queries. - * @private - */ - #filterService = inject(FilterService); - - /** - * Restores scroll position when navigating back to this component. - */ restoreScrollPosition = injectRestoreScrollPosition(); - /** - * Resource for fetching and managing the reward catalog data. - * Uses the FilterService to get the current query token and reacts to search triggers. - * @private - * @returns The reward catalog resource. - */ - rewardCatalogResource = createRewardCatalogResource(() => { - return { - queryToken: this.#filterService.query(), - searchTrigger: this.searchTrigger(), - }; - }); + searchTrigger = signal('initial'); - /** - * Computed signal for the current reward catalog response. - * @returns The latest reward catalog response or undefined. - */ - listResponseValue = computed(() => this.rewardCatalogResource.value()); - - /** - * Computed signal indicating whether the reward catalog resource is currently fetching data. - * @returns True if fetching, false otherwise. - */ - listFetching = computed( - () => this.rewardCatalogResource.status() === 'loading', - ); - - /** - * Computed signal for the reward catalog items to display. - * @returns Array of Item. - */ - items = computed(() => { - const value = this.listResponseValue(); - return value?.result ? value.result : []; - }); - - /** - * Computed signal for the total number of hits in the reward catalog. - * @returns Number of hits, or 0 if unavailable. - */ - hits = computed(() => { - const value = this.listResponseValue(); - return value?.hits ? value.hits : 0; - }); + #filterService = inject(FilterService); search(trigger: SearchTrigger): void { + this.searchTrigger.set(trigger); // Ist entweder 'scan', 'input', 'filter' oder 'orderBy' this.#filterService.commit(); - this.searchTrigger.set(trigger); } } diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-customer-card/reward-customer-card.component.css b/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-customer-card/reward-customer-card.component.css index e37c98ce2..dfefe9e30 100644 --- a/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-customer-card/reward-customer-card.component.css +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-customer-card/reward-customer-card.component.css @@ -1,3 +1,3 @@ :host { - @apply 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 gap-20 rounded-2xl bg-isa-neutral-400 p-6 text-isa-neutral-900; } diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-start-card/reward-start-card.component.css b/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-start-card/reward-start-card.component.css index 4d95c55ad..6be1996e9 100644 --- a/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-start-card/reward-start-card.component.css +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-header/reward-start-card/reward-start-card.component.css @@ -1,5 +1,5 @@ :host { - @apply h-32 w-full grid grid-cols-[1fr,auto] gap-6 rounded-2xl bg-isa-neutral-400 p-6 justify-between; + @apply h-[9.5rem] desktop:h-32 w-full grid grid-cols-[1fr,auto] gap-6 rounded-2xl bg-isa-neutral-400 p-6 justify-between; } .reward-start-card__title-container { diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-catalog-item/reward-catalog-item.component.html b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.css similarity index 100% rename from libs/checkout/feature/reward-catalog/src/lib/reward-catalog-item/reward-catalog-item.component.html rename to libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.css diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.html b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.html new file mode 100644 index 000000000..ad8855550 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.html @@ -0,0 +1,10 @@ + + + diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.ts new file mode 100644 index 000000000..874e0b24d --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item-select/reward-list-item-select.component.ts @@ -0,0 +1,39 @@ +import { + ChangeDetectionStrategy, + Component, + input, + linkedSignal, + inject, +} from '@angular/core'; +import { Item } from '@isa/catalogue/data-access'; +import { FormsModule } from '@angular/forms'; +import { CheckboxComponent } from '@isa/ui/input-controls'; +import { RewardCatalogStore } from '@isa/checkout/data-access'; + +@Component({ + selector: 'reward-list-item-select', + templateUrl: './reward-list-item-select.component.html', + styleUrl: './reward-list-item-select.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [FormsModule, CheckboxComponent], +}) +export class RewardListItemSelectComponent { + #store = inject(RewardCatalogStore); + + item = input.required(); + + itemSelected = linkedSignal(() => { + const itemId = this.item()?.id; + return !!itemId && !!this.#store.selectedItems()?.[itemId]; + }); + + setSelected(selected: boolean) { + const itemId = this.item()?.id; + if (itemId && selected) { + this.#store.selectItem(itemId, this.item()); + } + if (itemId && !selected) { + this.#store.removeItem(itemId); + } + } +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.css b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.css new file mode 100644 index 000000000..09203560d --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.css @@ -0,0 +1,15 @@ +:host { + @apply w-full; +} + +.ui-client-row { + @apply py-6 grid grid-cols-[0.75fr,0.25fr,auto]; +} + +.stock-row-tablet { + @apply row-start-1 self-end; +} + +.select-row-tablet { + @apply row-start-1 self-start mt-4; +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.html b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.html new file mode 100644 index 000000000..bd51e33f0 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.html @@ -0,0 +1,28 @@ +@let i = item(); + + + + + + + + + + + + diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.ts new file mode 100644 index 000000000..544af20a3 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list-item/reward-list-item.component.ts @@ -0,0 +1,34 @@ +import { + ChangeDetectionStrategy, + Component, + input, + linkedSignal, +} from '@angular/core'; +import { Item } from '@isa/catalogue/data-access'; +import { ClientRowImports, ItemRowDataImports } from '@isa/ui/item-rows'; +import { Breakpoint, breakpoint } from '@isa/ui/layout'; +import { ProductInfoRedemptionComponent } from '@isa/checkout/shared/product-info'; +import { StockInfoComponent } from '@isa/checkout/shared/product-info'; +import { RewardListItemSelectComponent } from './reward-list-item-select/reward-list-item-select.component'; + +@Component({ + selector: 'reward-list-item', + templateUrl: './reward-list-item.component.html', + styleUrl: './reward-list-item.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ClientRowImports, + ItemRowDataImports, + ProductInfoRedemptionComponent, + StockInfoComponent, + RewardListItemSelectComponent, + ], +}) +export class RewardListItemComponent { + item = input.required(); + + desktopBreakpoint = breakpoint([Breakpoint.DekstopL, Breakpoint.DekstopXL]); + productInfoOrientation = linkedSignal(() => { + return this.desktopBreakpoint() ? 'horizontal' : 'vertical'; + }); +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.css b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.css new file mode 100644 index 000000000..d249b6564 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.css @@ -0,0 +1,3 @@ +:host { + @apply w-full; +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.html b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.html new file mode 100644 index 000000000..9366b125d --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.html @@ -0,0 +1,64 @@ + + {{ hits() }} Einträge + + +@if (!!itemsLength()) { +
+ @for (item of items(); track item.id; let isLast = $last) { + @defer (on viewport) { + + @if (!isLast) { + + } + } @placeholder { +
+ +
+ } + } + + @if (listFetching()) { +
+ +
+ } @else if (renderPageTrigger()) { +
+ } +
+} @else if (renderSearchLoader()) { +
+ +
+} @else { + + + +} diff --git a/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.ts b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.ts new file mode 100644 index 000000000..19d1ccd76 --- /dev/null +++ b/libs/checkout/feature/reward-catalog/src/lib/reward-list/reward-list.component.ts @@ -0,0 +1,96 @@ +import { + ChangeDetectionStrategy, + Component, + inject, + linkedSignal, + model, +} from '@angular/core'; +import { FilterService, SearchTrigger } from '@isa/shared/filter'; +import { ButtonComponent, IconButtonComponent } from '@isa/ui/buttons'; +import { EmptyStateComponent } from '@isa/ui/empty-state'; +import { createRewardCatalogResource } from '../resources'; +import { RewardListItemComponent } from './reward-list-item/reward-list-item.component'; +import { InViewportDirective } from '@isa/ui/layout'; +import { RewardCatalogStore } from '@isa/checkout/data-access'; + +@Component({ + selector: 'reward-list', + templateUrl: './reward-list.component.html', + styleUrl: './reward-list.component.css', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + ButtonComponent, + IconButtonComponent, + EmptyStateComponent, + RewardListItemComponent, + InViewportDirective, + ], + host: { + '[class]': '"w-full flex flex-col gap-4"', + }, +}) +export class RewardListComponent { + searchTrigger = model('initial'); + + /** + * FilterService instance for managing filter state and queries. + * @private + */ + #filterService = inject(FilterService); + + /** + * RewardCatalogStore instance for managing reward catalog state. + * @private + */ + #rewardCatalogStore = inject(RewardCatalogStore); + + /** + * Resource for fetching and managing the reward catalog data. + * Uses the FilterService to get the current query token and reacts to search triggers. + * @private + * @returns The reward catalog resource. + */ + rewardCatalogResource = createRewardCatalogResource(() => { + return { + queryToken: this.#filterService.query(), + searchTrigger: this.searchTrigger(), + }; + }); + + /** + * LinkedSignal indicating whether the reward catalog resource is currently fetching data. + * @returns True if fetching, false otherwise. + */ + listFetching = linkedSignal( + () => this.rewardCatalogResource.status() === 'loading', + ); + + items = linkedSignal(() => this.#rewardCatalogStore.items()); + + itemsLength = linkedSignal(() => this.items().length); + + hits = linkedSignal(() => this.#rewardCatalogStore.hits()); + + renderSearchLoader = linkedSignal(() => { + return this.listFetching() && this.itemsLength() === 0; + }); + + renderPageTrigger = linkedSignal(() => { + if (this.listFetching()) return false; + return this.hits() > this.itemsLength(); + }); + + paging(inViewport: boolean) { + if (!inViewport) { + return; + } + + if (this.itemsLength() !== this.hits()) { + this.searchTrigger.set('reload'); + } + } + + resetFilter() { + this.#filterService.reset({ commit: true }); + } +} diff --git a/libs/checkout/shared/product-info/src/lib/destination-info/destination-info.component.html b/libs/checkout/shared/product-info/src/lib/destination-info/destination-info.component.html index 71259dc27..8e62a3eb7 100644 --- a/libs/checkout/shared/product-info/src/lib/destination-info/destination-info.component.html +++ b/libs/checkout/shared/product-info/src/lib/destination-info/destination-info.component.html @@ -12,8 +12,8 @@ @if (displayAddress()) { {{ branchName() }} | - } @else if (estimatedDelivery(); as delivery) { - Zustellung zwischen {{ delivery.start | date: 'E, dd.MM.' }} und - {{ delivery.stop | date: 'E, dd.MM.' }} + } @else if (estimatedDelivery()) { + Zustellung zwischen {{ estimatedDelivery().start | date: 'E, dd.MM.' }} und + {{ estimatedDelivery().stop | date: 'E, dd.MM.' }} } diff --git a/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.css b/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.css index 65222c8b8..053c4ddf2 100644 --- a/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.css +++ b/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.css @@ -1,5 +1,5 @@ :host { - @apply grid grid-flow-col gap-6 text-neutral-900; + @apply grid grid-flow-col desktop-large:grid-cols-[0.75fr,0.5fr] gap-6 text-neutral-900; } :host.vertical { diff --git a/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.ts b/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.ts index 6484c8d87..52ecb102a 100644 --- a/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.ts +++ b/libs/checkout/shared/product-info/src/lib/product-info/product-info-redemption.component.ts @@ -16,7 +16,7 @@ export type ProductInfoItem = { | 'manufacturer' | 'publicationDate' >; - redemptionPoints: number; + redemptionPoints?: number; }; @Component({ diff --git a/libs/checkout/shared/product-info/src/lib/stock-info/stock-info.component.ts b/libs/checkout/shared/product-info/src/lib/stock-info/stock-info.component.ts index 3f1cbd670..e8662b7d1 100644 --- a/libs/checkout/shared/product-info/src/lib/stock-info/stock-info.component.ts +++ b/libs/checkout/shared/product-info/src/lib/stock-info/stock-info.component.ts @@ -14,9 +14,7 @@ import { SkeletonLoaderComponent } from '@isa/ui/skeleton-loader'; export type StockInfoItem = { id: Item['id']; - catalogAvailability: Required< - Pick - >; + catalogAvailability: Pick; }; @Component({ diff --git a/libs/common/data-access/src/lib/operators/catch-response-args-error.ts b/libs/common/data-access/src/lib/operators/catch-response-args-error.ts index 5e42196db..da10a3b34 100644 --- a/libs/common/data-access/src/lib/operators/catch-response-args-error.ts +++ b/libs/common/data-access/src/lib/operators/catch-response-args-error.ts @@ -39,7 +39,5 @@ export const catchResponseArgsErrorPipe = (): OperatorFunction => } } - return throwError(() => - err instanceof Error ? err : new Error(String(err)), - ); + return throwError(() => err); }); diff --git a/libs/crm/data-access/src/lib/facades/customer-cards.facade.ts b/libs/crm/data-access/src/lib/facades/customer-cards.facade.ts index e97e6aa76..4c5d6f330 100644 --- a/libs/crm/data-access/src/lib/facades/customer-cards.facade.ts +++ b/libs/crm/data-access/src/lib/facades/customer-cards.facade.ts @@ -1,15 +1,15 @@ -import { inject, Injectable } from '@angular/core'; -import { CrmSearchService } from '../services'; -import { FetchCustomerCardsInput } from '../schemas'; - -@Injectable({ providedIn: 'root' }) -export class CustomerCardsFacade { - crmSearchService = inject(CrmSearchService); - - async get(params: FetchCustomerCardsInput, abortSignal?: AbortSignal) { - return await this.crmSearchService.fetchCustomerCards( - { ...params }, - abortSignal, - ); - } -} +import { inject, Injectable } from '@angular/core'; +import { CrmSearchService } from '../services'; +import { FetchCustomerCardsInput } from '../schemas'; + +@Injectable({ providedIn: 'root' }) +export class CustomerCardsFacade { + #crmSearchService = inject(CrmSearchService); + + async get(params: FetchCustomerCardsInput, abortSignal: AbortSignal) { + return await this.#crmSearchService.fetchCustomerCards( + { ...params }, + abortSignal, + ); + } +} diff --git a/libs/crm/data-access/src/lib/facades/selected-customer-id.facade.ts b/libs/crm/data-access/src/lib/facades/selected-customer-id.facade.ts index 86f7cc880..c4fdae141 100644 --- a/libs/crm/data-access/src/lib/facades/selected-customer-id.facade.ts +++ b/libs/crm/data-access/src/lib/facades/selected-customer-id.facade.ts @@ -3,17 +3,17 @@ import { CrmTabMetadataService } from '../services'; @Injectable({ providedIn: 'root' }) export class SelectedCustomerFacade { - crmTabMetadataService = inject(CrmTabMetadataService); + #crmTabMetadataService = inject(CrmTabMetadataService); set(tabId: number, customerId: number) { - this.crmTabMetadataService.setSelectedCustomerId(tabId, customerId); + this.#crmTabMetadataService.setSelectedCustomerId(tabId, customerId); } get(tab: number) { - return this.crmTabMetadataService.selectedCustomerId(tab); + return this.#crmTabMetadataService.selectedCustomerId(tab); } clear(tab: number) { - this.crmTabMetadataService.setSelectedCustomerId(tab, undefined); + this.#crmTabMetadataService.setSelectedCustomerId(tab, undefined); } } diff --git a/libs/crm/data-access/src/lib/services/crm-search.service.ts b/libs/crm/data-access/src/lib/services/crm-search.service.ts index 203782924..20c6ec492 100644 --- a/libs/crm/data-access/src/lib/services/crm-search.service.ts +++ b/libs/crm/data-access/src/lib/services/crm-search.service.ts @@ -1,75 +1,86 @@ -import { inject, Injectable } from '@angular/core'; -import { - CustomerService, - ResponseArgsOfCustomerDTO, - ResponseArgsOfIEnumerableOfBonusCardInfoDTO, -} from '@generated/swagger/crm-api'; -import { - FetchCustomerCardsInput, - FetchCustomerCardsSchema, - FetchCustomerInput, - FetchCustomerSchema, -} from '../schemas'; -import { - catchResponseArgsErrorPipe, - ResponseArgs, - takeUntilAborted, -} from '@isa/common/data-access'; -import { firstValueFrom } from 'rxjs'; -import { BonusCardInfo, Customer } from '../models'; - -@Injectable({ providedIn: 'root' }) -export class CrmSearchService { - #customerService = inject(CustomerService); - - async fetchCustomer( - params: FetchCustomerInput, - abortSignal?: AbortSignal, - ): Promise> { - const { customerId, eagerLoading } = FetchCustomerSchema.parse(params); - - let req$ = this.#customerService.CustomerGetCustomer({ - customerId, - eagerLoading, - }); - - if (abortSignal) { - req$ = req$.pipe(takeUntilAborted(abortSignal)); - } - - req$ = req$.pipe(catchResponseArgsErrorPipe()); - - const res = await firstValueFrom(req$); - - if (res.error) { - throw new Error(res.message); - } - - return res as ResponseArgs; - } - - async fetchCustomerCards( - params: FetchCustomerCardsInput, - abortSignal?: AbortSignal, - ): Promise> { - const { customerId } = FetchCustomerCardsSchema.parse(params); - - let req$ = this.#customerService.CustomerGetBonuscards(customerId); - - if (abortSignal) { - req$ = req$.pipe(takeUntilAborted(abortSignal)); - } - - req$ = req$.pipe( - catchResponseArgsErrorPipe(), - ); - - const res = await firstValueFrom(req$); - - if (res.error) { - throw new Error(res.message); - } - - return res as ResponseArgs; - } -} +import { inject, Injectable } from '@angular/core'; +import { + CustomerService, + ResponseArgsOfCustomerDTO, + ResponseArgsOfIEnumerableOfBonusCardInfoDTO, +} from '@generated/swagger/crm-api'; +import { + FetchCustomerCardsInput, + FetchCustomerCardsSchema, + FetchCustomerInput, + FetchCustomerSchema, +} from '../schemas'; +import { + catchResponseArgsErrorPipe, + ResponseArgs, + ResponseArgsError, + takeUntilAborted, +} from '@isa/common/data-access'; +import { firstValueFrom } from 'rxjs'; +import { BonusCardInfo, Customer } from '../models'; +import { logger } from '@isa/core/logging'; +import { HttpErrorResponse } from '@angular/common/http'; + +@Injectable({ providedIn: 'root' }) +export class CrmSearchService { + #customerService = inject(CustomerService); + #logger = logger(() => ({ + service: 'CrmSearchService', + })); + + async fetchCustomer( + params: FetchCustomerInput, + abortSignal: AbortSignal, + ): Promise> { + this.#logger.info('Fetching customer from API'); + const { customerId, eagerLoading } = FetchCustomerSchema.parse(params); + + const req$ = this.#customerService + .CustomerGetCustomer({ customerId, eagerLoading }) + .pipe( + takeUntilAborted(abortSignal), + catchResponseArgsErrorPipe(), + ); + + try { + const res = await firstValueFrom(req$); + this.#logger.debug('Successfully fetched customer'); + return res as ResponseArgs; + } catch (error: + | ResponseArgsError + | HttpErrorResponse + | Error + | unknown) { + this.#logger.error('Error fetching customer', error); + return undefined as unknown as ResponseArgs; + } + } + + async fetchCustomerCards( + params: FetchCustomerCardsInput, + abortSignal: AbortSignal, + ): Promise> { + this.#logger.info('Fetching customer bonuscards from API'); + const { customerId } = FetchCustomerCardsSchema.parse(params); + + const req$ = this.#customerService + .CustomerGetBonuscards(customerId) + .pipe( + takeUntilAborted(abortSignal), + catchResponseArgsErrorPipe(), + ); + + try { + const res = await firstValueFrom(req$); + this.#logger.debug('Successfully fetched customer bonuscards'); + return res as ResponseArgs; + } catch (error: + | ResponseArgsError + | HttpErrorResponse + | Error + | unknown) { + this.#logger.error('Error fetching customer cards', error); + return [] as unknown as ResponseArgs; + } + } +} diff --git a/libs/shared/filter/src/lib/controls-panel/controls-panel.component.ts b/libs/shared/filter/src/lib/controls-panel/controls-panel.component.ts index 9f5cf3a6d..0dc46de37 100644 --- a/libs/shared/filter/src/lib/controls-panel/controls-panel.component.ts +++ b/libs/shared/filter/src/lib/controls-panel/controls-panel.component.ts @@ -82,7 +82,7 @@ export class FilterControlsPanelComponent { * Signal tracking whether the viewport is at tablet size or above. * Used to determine responsive layout behavior for mobile vs desktop. */ - mobileBreakpoint = breakpoint([Breakpoint.Tablet]); + mobileBreakpoint = breakpoint([Breakpoint.Tablet, Breakpoint.Desktop]); /** * Signal controlling the visibility of the order-by toolbar on mobile devices.