Merged PR 1973: feat(customer-search): use navigation state for reward customer selection

feat(customer-search): use navigation state for reward customer selection

Replace tab metadata context flag with NavigationStateService for tracking
reward flow customer selection. Store return URL in preserved navigation
context instead of tab metadata 'context' field.

Benefits:
- Clean separation: tab metadata no longer polluted with flow state
- Automatic cleanup when tabs close (no manual cleanup needed)
- Survives intermediate navigations (e.g., address edit)
- Tab-scoped automatically via TabService integration

Changes:
- Remove `isRewardTab()` linkedSignal and tab metadata 'context' check
- Add NavigationStateService with 'select-customer' scope
- Store returnUrl in preserved context before navigation
- Restore context and navigate back on customer selection
- Update reward-start-card to preserve context on button click
- Remove reward-catalog context initialization (no longer needed)

Technical Details:
- Context stored in tab.metadata['navigation-contexts']['select-customer']
- Uses async methods: preserveContext(), restoreAndClearContext()
- Signal-based hasReturnUrl() for template reactivity
- Maintains existing button flow (checks hasReturnUrl signal)

Ref: #5368
This commit is contained in:
Nino Righi
2025-10-20 11:56:35 +00:00
committed by Lorenz Hilpert
parent b96d889da5
commit f549c59bc8
6 changed files with 55 additions and 539 deletions

View File

@@ -184,7 +184,7 @@
>Auswählen</shared-loader
>
</button>
} @else if (!isRewardTab()) {
} @else {
@if (shoppingCartHasNoItems$ | async) {
<button
type="button"
@@ -210,15 +210,4 @@
>
</button>
}
} @else {
<button
type="button"
(click)="continueReward()"
class="w-60 text-white text-lg bg-brand rounded-full px-5 py-3 absolute top-[calc(100vh-19.375rem)] left-1/2 -translate-x-1/2 font-bold disabled:bg-inactive-branch"
[disabled]="!(hasKundenkarte$ | async)"
>
<shared-loader [loading]="showLoader$ | async" spinnerSize="32"
>Auswählen</shared-loader
>
</button>
}

View File

@@ -80,10 +80,9 @@ export class CustomerDetailsViewMainComponent
customerService = inject(CrmCustomerService);
crmTabMetadataService = inject(CrmTabMetadataService);
private _rewardSelectionPopUpService = inject(RewardSelectionPopUpService);
tab = injectTab();
// Signal to track if return URL exists
hasReturnUrlSignal = signal(false);
// Signal to track if return URL exists (used directly in template)
readonly hasReturnUrl = signal(false);
fetching$ = combineLatest([
this._store.fetchingCustomer$,
@@ -104,11 +103,7 @@ export class CustomerDetailsViewMainComponent
async checkHasReturnUrl(): Promise<void> {
const hasContext =
await this._navigationState.hasPreservedContext('select-customer');
this.hasReturnUrlSignal.set(hasContext);
}
hasReturnUrl(): boolean {
return this.hasReturnUrlSignal();
this.hasReturnUrl.set(hasContext);
}
isBusy$ = this.select((s) => s.isBusy);
@@ -318,11 +313,6 @@ export class CustomerDetailsViewMainComponent
this.patchState({ payer });
}
isRewardTab = linkedSignal(() => {
const tab = this.tab();
return tab?.metadata?.['context'] === 'reward';
});
ngOnInit() {
// Check if we have a return URL context
this.checkHasReturnUrl();
@@ -367,7 +357,7 @@ export class CustomerDetailsViewMainComponent
@logAsync
// #5262 Für die Auswahl des Kunden im "Prämienshop-Modus" (Getrennt vom regulären Checkout-Prozess)
async continueReward() {
if (this.isRewardTab()) {
if (this.hasReturnUrl()) {
this._setSelectedCustomerIdInTab();
// Restore from preserved context (auto-scoped to current tab) and clean up

View File

@@ -17,7 +17,6 @@ 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';
import { TabService } from '@isa/core/tabs';
import { SelectedRewardShoppingCartResource } from '@isa/checkout/data-access';
import { SelectedCustomerBonusCardsResource } from '@isa/crm/data-access';
@@ -54,28 +53,14 @@ function querySettingsFactory() {
},
})
export class RewardCatalogComponent {
#tabService = inject(TabService);
restoreScrollPosition = injectRestoreScrollPosition();
searchTrigger = signal<SearchTrigger | 'reload' | 'initial'>('initial');
#filterService = inject(FilterService);
constructor() {
this.initRewardContext();
}
search(trigger: SearchTrigger): void {
this.searchTrigger.set(trigger); // Ist entweder 'scan', 'input', 'filter' oder 'orderBy'
this.#filterService.commit();
}
// Wichtig damit Kundensuche weiß, dass wir im Reward Kontext sind - Über Header CTA oder Kaufoptionen gelangt man zur Kundensuche
initRewardContext() {
const tabId = this.#tabService.activatedTabId();
if (tabId) {
this.#tabService.patchTabMetadata(tabId, { context: 'reward' });
}
}
}

View File

@@ -7,15 +7,15 @@
</p>
</div>
<a
<button
class="reward-start-card__select-cta"
data-which="select-customer"
data-what="select-customer"
uiButton
color="tertiary"
size="large"
[routerLink]="route().path"
[queryParams]="route().queryParams"
type="button"
(click)="onSelectCustomer()"
>
Kund*in auswählen
</a>
</button>

View File

@@ -1,16 +1,41 @@
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
import { ButtonComponent } from '@isa/ui/buttons';
import { injectTabId } from '@isa/core/tabs';
import { RouterLink } from '@angular/router';
import { Router } from '@angular/router';
import { getRouteToCustomer } from '../../helpers';
import { NavigationStateService } from '@isa/core/navigation';
@Component({
selector: 'reward-start-card',
templateUrl: './reward-start-card.component.html',
styleUrl: './reward-start-card.component.css',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ButtonComponent, RouterLink],
imports: [ButtonComponent],
})
export class RewardStartCardComponent {
readonly #navigationState = inject(NavigationStateService);
readonly #router = inject(Router);
tabId = injectTabId();
route = computed(() => getRouteToCustomer(this.tabId()));
/**
* Called when "Kund*in auswählen" button is clicked.
* Preserves the current URL as returnUrl before navigating to customer search.
*/
async onSelectCustomer() {
const customerRoute = getRouteToCustomer(this.tabId());
// Preserve context: Store current reward page URL to return to after customer selection
await this.#navigationState.preserveContext(
{
returnUrl: this.#router.url,
},
'select-customer',
);
// Navigate to customer search
await this.#router.navigate(customerRoute.path, {
queryParams: customerRoute.queryParams,
});
}
}

507
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff