mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1969: Reward Shopping Cart Implementation with Navigation State Management and Shipping Address Integration
1. Reward Shopping Cart Implementation - New shopping cart with quantity control and availability checking - Responsive shopping cart item component with improved CSS styling - Shipping address integration in cart - Customer reward card and billing/shipping address components 2. Navigation State Management Library (@isa/core/navigation) - New library with type-safe navigation context service (373 lines) - Navigation state service (287 lines) for temporary state between routes - Comprehensive test coverage (668 + 227 lines of tests) - Documentation (792 lines in README.md) - Replaces query parameters for passing temporary navigation context 3. CRM Shipping Address Services - New ShippingAddressService with fetching and validation - CustomerShippingAddressResource and CustomerShippingAddressesResource - Zod schemas for data validation 4. Additional Improvements - Enhanced searchbox accessibility with ARIA support - Availability data access rework for better fetching/mapping - Storybook tooltip variant support - Vitest JUnit and Cobertura reporting configuration Related work items: #5382, #5383, #5384
This commit is contained in:
committed by
Nino Righi
parent
f15848d5c0
commit
596ae1da1b
@@ -1,213 +1,224 @@
|
||||
<shared-loader [loading]="fetching$ | async" background="true" spinnerSize="32">
|
||||
<div class="overflow-scroll max-h-[calc(100vh-15rem)]">
|
||||
<div class="customer-details-header grid grid-flow-row pb-6">
|
||||
<div
|
||||
class="customer-details-header-actions flex flex-row justify-end pt-4 px-4"
|
||||
>
|
||||
<page-customer-menu
|
||||
[customerId]="customerId$ | async"
|
||||
[processId]="processId$ | async"
|
||||
[hasCustomerCard]="hasKundenkarte$ | async"
|
||||
[showCustomerDetails]="false"
|
||||
></page-customer-menu>
|
||||
</div>
|
||||
<div class="customer-details-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">
|
||||
{{ (isBusinessKonto$ | async) ? 'Firmendetails' : 'Kundendetails' }}
|
||||
</h1>
|
||||
<p>Sind Ihre Kundendaten korrekt?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="customer-details-customer-type flex flex-row justify-between items-center bg-surface-2 text-surface-2-content h-14"
|
||||
>
|
||||
<div
|
||||
class="pl-4 font-bold grid grid-flow-col justify-start items-center gap-2"
|
||||
>
|
||||
<shared-icon [icon]="customerType$ | async"></shared-icon>
|
||||
<span>
|
||||
{{ customerType$ | async }}
|
||||
</span>
|
||||
</div>
|
||||
@if (showEditButton$ | async) {
|
||||
@if (editRoute$ | async; as editRoute) {
|
||||
<a
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
@if (created$ | async; as created) {
|
||||
<div class="data-value">
|
||||
{{ created | date: 'dd.MM.yyyy' }} |
|
||||
{{ created | date: 'HH:mm' }} Uhr
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
<div class="data-value">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
@if (customerNumberDig$ | async; as customerNumberDig) {
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer-DIG</div>
|
||||
<div class="data-value">{{ customerNumberDig }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (customerNumberBeeline$ | async; as customerNumberBeeline) {
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer-BEELINE</div>
|
||||
<div class="data-value">{{ customerNumberBeeline }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (isBusinessKonto$ | async) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Anrede</div>
|
||||
<div class="data-value">{{ gender$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Titel</div>
|
||||
<div class="data-value">{{ title$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Nachname</div>
|
||||
<div class="data-value">{{ lastName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Vorname</div>
|
||||
<div class="data-value">{{ firstName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">E-Mail</div>
|
||||
<div class="data-value">{{ email$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Straße</div>
|
||||
<div class="data-value">{{ street$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Hausnr.</div>
|
||||
<div class="data-value">{{ streetNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">PLZ</div>
|
||||
<div class="data-value">{{ zipCode$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Ort</div>
|
||||
<div class="data-value">{{ city$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Adresszusatz</div>
|
||||
<div class="data-value">{{ info$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
@if (country$ | async; as country) {
|
||||
<div class="data-value">{{ country | country }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
<div class="data-value">{{ landline$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Mobilnr.</div>
|
||||
<div class="data-value">{{ mobile$ | async }}</div>
|
||||
</div>
|
||||
|
||||
@if (!(isBusinessKonto$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Geburtstag</div>
|
||||
<div class="data-value">
|
||||
{{ dateOfBirth$ | async | date: 'dd.MM.yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!(isBusinessKonto$ | async) && (organisationName$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
@if (!(isOnlineOrCustomerCardUser$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<page-details-main-view-billing-addresses></page-details-main-view-billing-addresses>
|
||||
<page-details-main-view-delivery-addresses></page-details-main-view-delivery-addresses>
|
||||
<div class="h-24"></div>
|
||||
</div>
|
||||
</shared-loader>
|
||||
|
||||
@if (!isRewardTab()) {
|
||||
@if (shoppingCartHasNoItems$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
(click)="continue()"
|
||||
class="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]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32"
|
||||
>Weiter zur Artikelsuche</shared-loader
|
||||
>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (shoppingCartHasItems$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
(click)="continue()"
|
||||
class="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]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32"
|
||||
>Weiter zum Warenkorb</shared-loader
|
||||
>
|
||||
</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>
|
||||
}
|
||||
<shared-loader [loading]="fetching$ | async" background="true" spinnerSize="32">
|
||||
<div class="overflow-scroll max-h-[calc(100vh-15rem)]">
|
||||
<div class="customer-details-header grid grid-flow-row pb-6">
|
||||
<div
|
||||
class="customer-details-header-actions flex flex-row justify-end pt-4 px-4"
|
||||
>
|
||||
<page-customer-menu
|
||||
[customerId]="customerId$ | async"
|
||||
[processId]="processId$ | async"
|
||||
[hasCustomerCard]="hasKundenkarte$ | async"
|
||||
[showCustomerDetails]="false"
|
||||
></page-customer-menu>
|
||||
</div>
|
||||
<div class="customer-details-header-body text-center -mt-3">
|
||||
<h1 class="text-[1.625rem] font-bold">
|
||||
{{ (isBusinessKonto$ | async) ? 'Firmendetails' : 'Kundendetails' }}
|
||||
</h1>
|
||||
<p>Sind Ihre Kundendaten korrekt?</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="customer-details-customer-type flex flex-row justify-between items-center bg-surface-2 text-surface-2-content h-14"
|
||||
>
|
||||
<div
|
||||
class="pl-4 font-bold grid grid-flow-col justify-start items-center gap-2"
|
||||
>
|
||||
<shared-icon [icon]="customerType$ | async"></shared-icon>
|
||||
<span>
|
||||
{{ customerType$ | async }}
|
||||
</span>
|
||||
</div>
|
||||
@if (showEditButton$ | async) {
|
||||
@if (editRoute$ | async; as editRoute) {
|
||||
<a
|
||||
[routerLink]="editRoute.path"
|
||||
[queryParams]="editRoute.queryParams"
|
||||
[queryParamsHandling]="'merge'"
|
||||
class="btn btn-label font-bold text-brand"
|
||||
>
|
||||
Bearbeiten
|
||||
</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="customer-details-customer-main-data px-5 py-3 grid grid-flow-row gap-3"
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Erstellungsdatum</div>
|
||||
@if (created$ | async; as created) {
|
||||
<div class="data-value">
|
||||
{{ created | date: 'dd.MM.yyyy' }} |
|
||||
{{ created | date: 'HH:mm' }} Uhr
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer</div>
|
||||
<div class="data-value">{{ customerNumber$ | async }}</div>
|
||||
</div>
|
||||
@if (customerNumberDig$ | async; as customerNumberDig) {
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer-DIG</div>
|
||||
<div class="data-value">{{ customerNumberDig }}</div>
|
||||
</div>
|
||||
}
|
||||
@if (customerNumberBeeline$ | async; as customerNumberBeeline) {
|
||||
<div class="flex flex-row">
|
||||
<div class="data-label">Kundennummer-BEELINE</div>
|
||||
<div class="data-value">{{ customerNumberBeeline }}</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (isBusinessKonto$ | async) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Anrede</div>
|
||||
<div class="data-value">{{ gender$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Titel</div>
|
||||
<div class="data-value">{{ title$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Nachname</div>
|
||||
<div class="data-value">{{ lastName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Vorname</div>
|
||||
<div class="data-value">{{ firstName$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">E-Mail</div>
|
||||
<div class="data-value">{{ email$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Straße</div>
|
||||
<div class="data-value">{{ street$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Hausnr.</div>
|
||||
<div class="data-value">{{ streetNumber$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">PLZ</div>
|
||||
<div class="data-value">{{ zipCode$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Ort</div>
|
||||
<div class="data-value">{{ city$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Adresszusatz</div>
|
||||
<div class="data-value">{{ info$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Land</div>
|
||||
@if (country$ | async; as country) {
|
||||
<div class="data-value">{{ country | country }}</div>
|
||||
}
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Festnetznr.</div>
|
||||
<div class="data-value">{{ landline$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Mobilnr.</div>
|
||||
<div class="data-value">{{ mobile$ | async }}</div>
|
||||
</div>
|
||||
|
||||
@if (!(isBusinessKonto$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Geburtstag</div>
|
||||
<div class="data-value">
|
||||
{{ dateOfBirth$ | async | date: 'dd.MM.yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!(isBusinessKonto$ | async) && (organisationName$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Firmenname</div>
|
||||
<div class="data-value">{{ organisationName$ | async }}</div>
|
||||
</div>
|
||||
@if (!(isOnlineOrCustomerCardUser$ | async)) {
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">Abteilung</div>
|
||||
<div class="data-value">{{ department$ | async }}</div>
|
||||
</div>
|
||||
<div class="customer-details-customer-main-row">
|
||||
<div class="data-label">USt-ID</div>
|
||||
<div class="data-value">{{ vatId$ | async }}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<page-details-main-view-billing-addresses></page-details-main-view-billing-addresses>
|
||||
<page-details-main-view-delivery-addresses></page-details-main-view-delivery-addresses>
|
||||
<div class="h-24"></div>
|
||||
</div>
|
||||
</shared-loader>
|
||||
|
||||
@if (hasReturnUrl()) {
|
||||
<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>
|
||||
} @else if (!isRewardTab()) {
|
||||
@if (shoppingCartHasNoItems$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
(click)="continue()"
|
||||
class="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]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32"
|
||||
>Weiter zur Artikelsuche</shared-loader
|
||||
>
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (shoppingCartHasItems$ | async) {
|
||||
<button
|
||||
type="button"
|
||||
(click)="continue()"
|
||||
class="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]="showLoader$ | async"
|
||||
>
|
||||
<shared-loader [loading]="showLoader$ | async" spinnerSize="32"
|
||||
>Weiter zum Warenkorb</shared-loader
|
||||
>
|
||||
</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>
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
OnDestroy,
|
||||
inject,
|
||||
linkedSignal,
|
||||
signal,
|
||||
} from '@angular/core';
|
||||
import { Subject, combineLatest } from 'rxjs';
|
||||
import { first, map, switchMap, takeUntil } from 'rxjs/operators';
|
||||
@@ -39,6 +40,7 @@ import { injectTab } from '@isa/core/tabs';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { CrmTabMetadataService, Customer } from '@isa/crm/data-access';
|
||||
import { CustomerAdapter } from '@isa/checkout/data-access';
|
||||
import { NavigationStateService } from '@isa/core/navigation';
|
||||
|
||||
export interface CustomerDetailsViewMainState {
|
||||
isBusy: boolean;
|
||||
@@ -68,12 +70,16 @@ export class CustomerDetailsViewMainComponent
|
||||
private _router = inject(Router);
|
||||
private _activatedRoute = inject(ActivatedRoute);
|
||||
private _genderSettings = inject(GenderSettingsService);
|
||||
private _navigationState = inject(NavigationStateService);
|
||||
private _onDestroy$ = new Subject<void>();
|
||||
|
||||
customerService = inject(CrmCustomerService);
|
||||
crmTabMetadataService = inject(CrmTabMetadataService);
|
||||
tab = injectTab();
|
||||
|
||||
// Signal to track if return URL exists
|
||||
hasReturnUrlSignal = signal(false);
|
||||
|
||||
fetching$ = combineLatest([
|
||||
this._store.fetchingCustomer$,
|
||||
this._store.fetchingCustomerList$,
|
||||
@@ -81,6 +87,24 @@ export class CustomerDetailsViewMainComponent
|
||||
map(([fetchingCustomer, fetchingList]) => fetchingCustomer || fetchingList),
|
||||
);
|
||||
|
||||
async getReturnUrlFromContext(): Promise<string | null> {
|
||||
// Get from preserved context (survives intermediate navigations, auto-scoped to tab)
|
||||
const context = await this._navigationState.restoreContext<{
|
||||
returnUrl?: string;
|
||||
}>('select-customer');
|
||||
|
||||
return context?.returnUrl ?? null;
|
||||
}
|
||||
|
||||
async checkHasReturnUrl(): Promise<void> {
|
||||
const hasContext = await this._navigationState.hasPreservedContext('select-customer');
|
||||
this.hasReturnUrlSignal.set(hasContext);
|
||||
}
|
||||
|
||||
hasReturnUrl(): boolean {
|
||||
return this.hasReturnUrlSignal();
|
||||
}
|
||||
|
||||
isBusy$ = this.select((s) => s.isBusy);
|
||||
|
||||
showLoader$ = combineLatest([this.fetching$, this.isBusy$]).pipe(
|
||||
@@ -294,6 +318,9 @@ export class CustomerDetailsViewMainComponent
|
||||
});
|
||||
|
||||
ngOnInit() {
|
||||
// Check if we have a return URL context
|
||||
this.checkHasReturnUrl();
|
||||
|
||||
this.processId$
|
||||
.pipe(
|
||||
takeUntil(this._onDestroy$),
|
||||
@@ -339,6 +366,18 @@ export class CustomerDetailsViewMainComponent
|
||||
this.processId,
|
||||
this.customer.id,
|
||||
);
|
||||
|
||||
// Restore from preserved context (auto-scoped to current tab) and clean up
|
||||
const context = await this._navigationState.restoreAndClearContext<{
|
||||
returnUrl?: string;
|
||||
}>('select-customer');
|
||||
|
||||
if (context?.returnUrl) {
|
||||
await this._router.navigateByUrl(context.returnUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// No returnUrl found, navigate to default reward page
|
||||
await this._router.navigate(['/', this.processId, 'reward']);
|
||||
}
|
||||
}
|
||||
@@ -402,15 +441,7 @@ export class CustomerDetailsViewMainComponent
|
||||
this._router.navigate(path);
|
||||
}
|
||||
|
||||
try {
|
||||
} catch (error) {
|
||||
this._modalService.error(
|
||||
'Warenkorb kann dem Kunden nicht zugewiesen werden',
|
||||
error,
|
||||
);
|
||||
} finally {
|
||||
this.setIsBusy(false);
|
||||
}
|
||||
this.setIsBusy(false);
|
||||
}
|
||||
|
||||
@logAsync
|
||||
@@ -475,7 +506,7 @@ export class CustomerDetailsViewMainComponent
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
} catch {
|
||||
this._modalService.open({
|
||||
content: MessageModalComponent,
|
||||
title: 'Warenkorb kann dem Kunden nicht zugewiesen werden',
|
||||
@@ -563,6 +594,10 @@ export class CustomerDetailsViewMainComponent
|
||||
processId: this.processId,
|
||||
customerDto: this.customer,
|
||||
});
|
||||
this.crmTabMetadataService.setSelectedCustomerId(
|
||||
this.processId,
|
||||
this.customer.id,
|
||||
);
|
||||
}
|
||||
|
||||
@log
|
||||
|
||||
@@ -1,73 +1,84 @@
|
||||
<div class="searchbox-input-wrapper">
|
||||
<div class="searchbox-hint-wrapper">
|
||||
<input
|
||||
id="searchbox"
|
||||
class="searchbox-input"
|
||||
autocomplete="off"
|
||||
#input
|
||||
type="text"
|
||||
[placeholder]="placeholder"
|
||||
[(ngModel)]="query"
|
||||
(ngModelChange)="setQuery($event, true, true)"
|
||||
(focus)="clearHint(); focused.emit(true)"
|
||||
(blur)="focused.emit(false)"
|
||||
(keyup)="onKeyup($event)"
|
||||
(keyup.enter)="
|
||||
tracker.trackEvent({ action: 'keyup enter', name: 'search' })
|
||||
"
|
||||
matomoTracker
|
||||
#tracker="matomo"
|
||||
matomoCategory="searchbox"
|
||||
/>
|
||||
@if (showHint) {
|
||||
<div class="searchbox-hint" (click)="focus()">
|
||||
{{ hint }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (input.value) {
|
||||
<button
|
||||
(click)="clear(); focus()"
|
||||
tabindex="-1"
|
||||
class="searchbox-clear-btn"
|
||||
type="button"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
@if (!loading) {
|
||||
@if (!showScannerButton) {
|
||||
<button
|
||||
tabindex="0"
|
||||
class="searchbox-search-btn"
|
||||
type="button"
|
||||
(click)="emitSearch()"
|
||||
[disabled]="completeValue !== query"
|
||||
matomoClickAction="click"
|
||||
matomoClickCategory="searchbox"
|
||||
matomoClickName="search"
|
||||
>
|
||||
<ui-icon icon="search" size="1.5rem"></ui-icon>
|
||||
</button>
|
||||
}
|
||||
@if (showScannerButton) {
|
||||
<button
|
||||
tabindex="0"
|
||||
class="searchbox-scan-btn"
|
||||
type="button"
|
||||
(click)="startScan()"
|
||||
matomoClickAction="open"
|
||||
matomoClickCategory="searchbox"
|
||||
matomoClickName="scanner"
|
||||
>
|
||||
<shared-icon icon="barcode-scan" [size]="32"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@if (loading) {
|
||||
<div class="searchbox-load-indicator">
|
||||
<ui-icon icon="spinner" size="32px"></ui-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<ng-content select="ui-autocomplete"></ng-content>
|
||||
<div class="searchbox-input-wrapper" role="search">
|
||||
<div class="searchbox-hint-wrapper">
|
||||
<input
|
||||
id="searchbox"
|
||||
class="searchbox-input"
|
||||
autocomplete="off"
|
||||
#input
|
||||
type="text"
|
||||
[placeholder]="placeholder"
|
||||
[(ngModel)]="query"
|
||||
(ngModelChange)="setQuery($event, true, true)"
|
||||
(focus)="clearHint(); focused.emit(true)"
|
||||
(blur)="focused.emit(false)"
|
||||
(keyup)="onKeyup($event)"
|
||||
(keyup.enter)="
|
||||
tracker.trackEvent({ action: 'keyup enter', name: 'search' })
|
||||
"
|
||||
matomoTracker
|
||||
#tracker="matomo"
|
||||
matomoCategory="searchbox"
|
||||
aria-label="Search input"
|
||||
aria-autocomplete="list"
|
||||
[attr.aria-expanded]="autocomplete?.opend || null"
|
||||
[attr.aria-controls]="autocomplete?.opend ? 'searchbox-autocomplete' : null"
|
||||
[attr.aria-activedescendant]="autocomplete?.activeItem ? 'searchbox-item-' + autocomplete?.listKeyManager?.activeItemIndex : null"
|
||||
[attr.aria-busy]="loading || null"
|
||||
[attr.aria-describedby]="showHint ? 'searchbox-hint' : null"
|
||||
/>
|
||||
@if (showHint) {
|
||||
<div id="searchbox-hint" class="searchbox-hint" (click)="focus()" aria-hidden="true">
|
||||
{{ hint }}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (input.value) {
|
||||
<button
|
||||
(click)="clear(); focus()"
|
||||
tabindex="-1"
|
||||
class="searchbox-clear-btn"
|
||||
type="button"
|
||||
aria-label="Clear"
|
||||
>
|
||||
<shared-icon icon="close" [size]="32" aria-hidden="true"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
@if (!loading) {
|
||||
@if (!showScannerButton) {
|
||||
<button
|
||||
tabindex="0"
|
||||
class="searchbox-search-btn"
|
||||
type="button"
|
||||
(click)="emitSearch()"
|
||||
[disabled]="completeValue !== query"
|
||||
[attr.aria-disabled]="completeValue !== query || null"
|
||||
matomoClickAction="click"
|
||||
matomoClickCategory="searchbox"
|
||||
matomoClickName="search"
|
||||
aria-label="Search"
|
||||
>
|
||||
<ui-icon icon="search" size="1.5rem" aria-hidden="true"></ui-icon>
|
||||
</button>
|
||||
}
|
||||
@if (showScannerButton) {
|
||||
<button
|
||||
tabindex="0"
|
||||
class="searchbox-scan-btn"
|
||||
type="button"
|
||||
(click)="startScan()"
|
||||
matomoClickAction="open"
|
||||
matomoClickCategory="searchbox"
|
||||
matomoClickName="scanner"
|
||||
aria-label="Scan barcode"
|
||||
>
|
||||
<shared-icon icon="barcode-scan" [size]="32" aria-hidden="true"></shared-icon>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
@if (loading) {
|
||||
<div class="searchbox-load-indicator" role="status" aria-live="polite" aria-label="Loading search results">
|
||||
<ui-icon icon="spinner" size="32px" aria-hidden="true"></ui-icon>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<ng-content select="ui-autocomplete"></ng-content>
|
||||
|
||||
Reference in New Issue
Block a user