diff --git a/apps/isa-app/src/page/customer/customer-search/kundenkarte-main-view/kundenkarte-main-view.component.html b/apps/isa-app/src/page/customer/customer-search/kundenkarte-main-view/kundenkarte-main-view.component.html
index 73e9ad0c6..19229a385 100644
--- a/apps/isa-app/src/page/customer/customer-search/kundenkarte-main-view/kundenkarte-main-view.component.html
+++ b/apps/isa-app/src/page/customer/customer-search/kundenkarte-main-view/kundenkarte-main-view.component.html
@@ -17,10 +17,18 @@
+
+
-
-
}
{
- const cards = this._bonusCardsResource.resource.value();
+ const cards = this.#bonusCardsResource.resource.value();
const firstActiveCard = cards?.find((card) => card.isActive);
return firstActiveCard?.code;
});
@@ -83,14 +86,24 @@ export class KundenkarteMainViewComponent implements OnDestroy {
effect(() => {
const customerId = this.customerId();
if (customerId) {
- this._bonusCardsResource.params({ customerId: Number(customerId) });
+ this.#bonusCardsResource.params({ customerId: Number(customerId) });
}
});
}
- reloadCardTransactions() {
+ /**
+ * Reloads both card transactions and bonus cards resources after a 500ms delay.
+ * Only triggers reload if the resource is not currently loading to prevent concurrent requests.
+ */
+ reloadCardTransactionsAndCards() {
this.#reloadTimeoutId = setTimeout(() => {
- this.#cardTransactionsResource.resource.reload();
+ if (!this.#cardTransactionsResource.resource.isLoading()) {
+ this.#cardTransactionsResource.resource.reload();
+ }
+
+ if (!this.#bonusCardsResource.resource.isLoading()) {
+ this.#bonusCardsResource.resource.reload();
+ }
}, 500);
}
diff --git a/libs/crm/feature/customer-booking/src/lib/crm-feature-customer-booking/crm-feature-customer-booking.component.ts b/libs/crm/feature/customer-booking/src/lib/crm-feature-customer-booking/crm-feature-customer-booking.component.ts
index 63feb2245..e85a006a5 100644
--- a/libs/crm/feature/customer-booking/src/lib/crm-feature-customer-booking/crm-feature-customer-booking.component.ts
+++ b/libs/crm/feature/customer-booking/src/lib/crm-feature-customer-booking/crm-feature-customer-booking.component.ts
@@ -1,159 +1,149 @@
-import {
- ChangeDetectionStrategy,
- Component,
- signal,
- computed,
- input,
- inject,
- OnDestroy,
-} from '@angular/core';
-import { FormsModule } from '@angular/forms';
-import { PositiveIntegerInputDirective } from '@isa/utils/positive-integer-input';
-import { ButtonComponent } from '@isa/ui/buttons';
-import {
- CustomerBookingReasonsResource,
- CustomerCardBookingFacade,
- CustomerCardTransactionsResource,
-} from '@isa/crm/data-access';
-import {
- injectFeedbackDialog,
- injectFeedbackErrorDialog,
-} from '@isa/ui/dialog';
-import { logger } from '@isa/core/logging';
-import {
- DropdownButtonComponent,
- DropdownOptionComponent,
-} from '@isa/ui/input-controls';
-import { TooltipIconComponent } from '@isa/ui/tooltip';
-
-@Component({
- selector: 'crm-customer-booking',
- imports: [
- FormsModule,
- PositiveIntegerInputDirective,
- ButtonComponent,
- DropdownButtonComponent,
- DropdownOptionComponent,
- TooltipIconComponent,
- ],
- templateUrl: './crm-feature-customer-booking.component.html',
- styleUrl: './crm-feature-customer-booking.component.css',
- changeDetection: ChangeDetectionStrategy.OnPush,
- providers: [CustomerBookingReasonsResource],
-})
-export class CrmFeatureCustomerBookingComponent implements OnDestroy {
- #reloadTimeoutId?: ReturnType;
- #logger = logger(() => ({
- component: 'CrmFeatureCustomerBookingComponent',
- }));
- #customerCardBookingFacade = inject(CustomerCardBookingFacade);
- #bookingReasonsResource = inject(CustomerBookingReasonsResource);
- #transactionResource = inject(CustomerCardTransactionsResource);
- #errorFeedbackDialog = injectFeedbackErrorDialog();
- #feedbackDialog = injectFeedbackDialog();
- readonly cardCode = input(undefined);
-
- readonly bookingReasons = this.#bookingReasonsResource.resource.value;
- readonly bookingReasonsLoading =
- this.#bookingReasonsResource.resource.isLoading;
-
- points = signal(undefined);
- selectedReasonKey = signal(undefined);
- isBooking = signal(false);
-
- selectedReason = computed(() => {
- const key = this.selectedReasonKey();
- const reasons = this.bookingReasons();
- return reasons?.find((r) => r.key === key);
- });
-
- calculatedPoints = computed(() => {
- const reason = this.selectedReason();
- const pointsValue = this.points();
- if (!reason || !pointsValue) return 0;
- return pointsValue * (reason.value ?? 1);
- });
-
- disableBooking = computed(() => {
- return (
- this.isBooking() ||
- this.bookingReasonsLoading() ||
- !this.selectedReasonKey() ||
- !this.points() ||
- this.points() === 0
- );
- });
-
- dropdownLabel = computed(() => {
- const reason = this.selectedReason()?.label;
- return reason ?? 'Buchungstyp';
- });
-
- async booking() {
- this.isBooking.set(true);
- try {
- const cardCode = this.cardCode();
- const reason = this.selectedReason();
- const calculatedPoints = this.calculatedPoints();
-
- if (!cardCode) {
- throw new Error('Kein Karten-Code vorhanden');
- }
-
- if (!reason) {
- throw new Error('Kein Buchungsgrund ausgewählt');
- }
-
- if (calculatedPoints === 0) {
- throw new Error('Punktezahl muss größer als 0 sein');
- }
-
- const currentBookingPartnerStore =
- await this.#customerCardBookingFacade.fetchCurrentBookingPartnerStore();
- const storeId = currentBookingPartnerStore?.key;
-
- await this.#customerCardBookingFacade.addBooking({
- cardCode,
- booking: {
- points: calculatedPoints,
- reason: reason.key,
- storeId: storeId,
- },
- });
-
- this.#feedbackDialog({
- data: {
- message: `${reason.label} erfolgreich durchgeführt`,
- },
- });
- this.reloadTransactionHistory();
- } catch (error: any) {
- this.#logger.error('Booking Failed', () => ({ error }));
- this.#errorFeedbackDialog({
- data: {
- errorMessage: error?.message ?? 'Buchen/Stornieren fehlgeschlagen',
- },
- });
- } finally {
- this.isBooking.set(false);
- this.resetInputs();
- }
- }
-
- resetInputs() {
- this.points.set(undefined);
- this.selectedReasonKey.set(undefined);
- }
-
- reloadTransactionHistory() {
- this.#reloadTimeoutId = setTimeout(() => {
- this.#transactionResource.resource.reload();
- }, 500);
- }
-
- ngOnDestroy(): void {
- if (this.#reloadTimeoutId) {
- clearTimeout(this.#reloadTimeoutId);
- }
- }
-}
+import {
+ ChangeDetectionStrategy,
+ Component,
+ signal,
+ computed,
+ input,
+ inject,
+ output,
+} from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { PositiveIntegerInputDirective } from '@isa/utils/positive-integer-input';
+import { ButtonComponent } from '@isa/ui/buttons';
+import {
+ CustomerBookingReasonsResource,
+ CustomerCardBookingFacade,
+} from '@isa/crm/data-access';
+import {
+ injectFeedbackDialog,
+ injectFeedbackErrorDialog,
+} from '@isa/ui/dialog';
+import { logger } from '@isa/core/logging';
+import {
+ DropdownButtonComponent,
+ DropdownOptionComponent,
+} from '@isa/ui/input-controls';
+import { TooltipIconComponent } from '@isa/ui/tooltip';
+
+@Component({
+ selector: 'crm-customer-booking',
+ imports: [
+ FormsModule,
+ PositiveIntegerInputDirective,
+ ButtonComponent,
+ DropdownButtonComponent,
+ DropdownOptionComponent,
+ TooltipIconComponent,
+ ],
+ templateUrl: './crm-feature-customer-booking.component.html',
+ styleUrl: './crm-feature-customer-booking.component.css',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ providers: [CustomerBookingReasonsResource],
+})
+export class CrmFeatureCustomerBookingComponent {
+ #logger = logger(() => ({
+ component: 'CrmFeatureCustomerBookingComponent',
+ }));
+ #customerCardBookingFacade = inject(CustomerCardBookingFacade);
+ #bookingReasonsResource = inject(CustomerBookingReasonsResource);
+ #errorFeedbackDialog = injectFeedbackErrorDialog();
+ #feedbackDialog = injectFeedbackDialog();
+ readonly cardCode = input(undefined);
+
+ readonly booked = output();
+
+ readonly bookingReasons = this.#bookingReasonsResource.resource.value;
+ readonly bookingReasonsLoading =
+ this.#bookingReasonsResource.resource.isLoading;
+
+ points = signal(undefined);
+ selectedReasonKey = signal(undefined);
+ isBooking = signal(false);
+
+ selectedReason = computed(() => {
+ const key = this.selectedReasonKey();
+ const reasons = this.bookingReasons();
+ return reasons?.find((r) => r.key === key);
+ });
+
+ calculatedPoints = computed(() => {
+ const reason = this.selectedReason();
+ const pointsValue = this.points();
+ if (!reason || !pointsValue) return 0;
+ return pointsValue * (reason.value ?? 1);
+ });
+
+ disableBooking = computed(() => {
+ return (
+ this.isBooking() ||
+ this.bookingReasonsLoading() ||
+ !this.selectedReasonKey() ||
+ !this.points() ||
+ this.points() === 0
+ );
+ });
+
+ dropdownLabel = computed(() => {
+ const reason = this.selectedReason()?.label;
+ return reason ?? 'Buchungstyp';
+ });
+
+ async booking() {
+ this.isBooking.set(true);
+ try {
+ const cardCode = this.cardCode();
+ const reason = this.selectedReason();
+ const calculatedPoints = this.calculatedPoints();
+
+ if (!cardCode) {
+ throw new Error('Kein Karten-Code vorhanden');
+ }
+
+ if (!reason) {
+ throw new Error('Kein Buchungsgrund ausgewählt');
+ }
+
+ if (calculatedPoints === 0) {
+ throw new Error('Punktezahl muss größer als 0 sein');
+ }
+
+ const currentBookingPartnerStore =
+ await this.#customerCardBookingFacade.fetchCurrentBookingPartnerStore();
+ const storeId = currentBookingPartnerStore?.key;
+
+ await this.#customerCardBookingFacade.addBooking({
+ cardCode,
+ booking: {
+ points: calculatedPoints,
+ reason: reason.key,
+ storeId: storeId,
+ },
+ });
+
+ this.#feedbackDialog({
+ data: {
+ message: `${reason.label} erfolgreich durchgeführt`,
+ },
+ });
+ this.booked.emit();
+ } catch (error: unknown) {
+ this.#logger.error('Booking Failed', () => ({ error }));
+ this.#errorFeedbackDialog({
+ data: {
+ errorMessage:
+ error instanceof Error
+ ? error.message
+ : 'Buchen/Stornieren fehlgeschlagen',
+ },
+ });
+ } finally {
+ this.isBooking.set(false);
+ this.resetInputs();
+ }
+ }
+
+ resetInputs() {
+ this.points.set(undefined);
+ this.selectedReasonKey.set(undefined);
+ }
+}
diff --git a/libs/crm/feature/customer-card-transactions/src/lib/crm-feature-customer-card-transactions/crm-feature-customer-card-transactions.component.ts b/libs/crm/feature/customer-card-transactions/src/lib/crm-feature-customer-card-transactions/crm-feature-customer-card-transactions.component.ts
index 19c5ab9d1..37257f171 100644
--- a/libs/crm/feature/customer-card-transactions/src/lib/crm-feature-customer-card-transactions/crm-feature-customer-card-transactions.component.ts
+++ b/libs/crm/feature/customer-card-transactions/src/lib/crm-feature-customer-card-transactions/crm-feature-customer-card-transactions.component.ts
@@ -5,6 +5,7 @@ import {
input,
effect,
computed,
+ output,
} from '@angular/core';
import { DatePipe, DecimalPipe } from '@angular/common';
import { CdkTableModule } from '@angular/cdk/table';
@@ -53,6 +54,8 @@ export class CrmFeatureCustomerCardTransactionsComponent {
*/
readonly cardCode = input(undefined);
+ readonly reload = output();
+
/**
* Exposed resource signals for template
*/
@@ -90,8 +93,11 @@ export class CrmFeatureCustomerCardTransactionsComponent {
});
}
+ /**
+ * Emits reload event to notify parent component.
+ * Parent is responsible for reloading resources to coordinate multi-resource updates.
+ */
refresh() {
- this.#transactionsResource.params({ cardCode: this.cardCode() });
- this.#transactionsResource.resource.reload();
+ this.reload.emit();
}
}