Files

@isa/crm/feature/customer-loyalty-cards

Customer loyalty cards feature module for displaying and managing customer bonus cards (Kundenkarten) with points tracking and card management actions.

Overview

This feature library provides a complete customer loyalty cards management interface for the CRM module. It displays all bonus cards associated with a customer, including:

  • Points summary with total Lesepunkte (reading points) from the primary card
  • Call-to-action button to navigate to the Prämienshop (rewards shop)
  • Interactive carousel for browsing multiple customer cards with barcodes
  • Card management actions: add new cards, lock/unlock cards
  • Visual indication of blocked cards with overlay states
  • Automatic sorting of blocked cards to the end of the carousel

The main component uses the Resource API pattern via CustomerBonusCardsResource for reactive data loading with automatic race condition prevention.

Architecture

This is a feature library (prefix: crm) containing:

  • Main container component that orchestrates the feature
  • Presentational components for cards, points summary, and carousel
  • Integration with @isa/crm/data-access for state management
  • Reactive data loading using Angular's Resource API

Components

CustomerLoyaltyCardsComponent

Main container component for the customer loyalty cards feature.

Selector: crm-customer-loyalty-cards

Inputs:

  • customerId: number (required) - Customer ID to load loyalty cards for. When changed, triggers automatic reload of bonus cards.
  • tabId: number (required) - Tab ID for telemetry/logging purposes.

Outputs:

  • navigateToPraemienshop: void - Emitted when user clicks the "Zum Prämienshop" button. Parent must handle navigation using the autoTriggerContinueFn pattern.
  • cardUpdated: void - Emitted when a card has been added, locked, or unlocked. Parent should reload cards and transactions.

State:

  • cards: Signal<BonusCardInfo[] | undefined> - All bonus cards for the selected customer
  • isLoading: Signal<boolean> - Loading state for bonus cards
  • error: Signal<Error | undefined> - Error state from loading bonus cards

Example:

<crm-customer-loyalty-cards
  [customerId]="selectedCustomerId()"
  [tabId]="currentTabId()"
  (navigateToPraemienshop)="handlePraemienshopNavigation()"
  (cardUpdated)="reloadCardsAndTransactions()"
/>

CustomerCardPointsSummaryComponent

Displays the points summary with CTA to Prämienshop.

Selector: crm-customer-card-points-summary

Inputs:

  • cards: BonusCardInfo[] (required) - All bonus cards for the customer

Outputs:

  • navigateToPraemienshop: void - Emitted when user clicks "Zum Prämienshop" button

Features:

  • Extracts total points from primary card
  • Formats points with German thousands separator (dot)
  • Centered layout matching Figma design

CustomerCardsCarouselComponent

Carousel container for displaying multiple customer loyalty cards with horizontal scrolling.

Selector: crm-customer-cards-carousel

Inputs:

  • cards: BonusCardInfo[] (required) - All bonus cards to display in carousel

Outputs:

  • cardLocked: void - Emitted when a card has been successfully locked or unlocked

Features:

  • Horizontal scrolling with navigation arrows (via @isa/ui/carousel)
  • Automatic sorting: blocked cards always appear at the end
  • Gap spacing between cards
  • Smooth scrolling animations

CustomerCardComponent

Individual customer loyalty card display component.

Selector: crm-customer-card

Inputs:

  • card: BonusCardInfo (required) - Bonus card data to display

Outputs:

  • cardLocked: void - Emitted when a card has been successfully locked or unlocked

Features:

  • Displays card type (Kundenkarte/Mitarbeitendenkarte)
  • Shows card number and customer name
  • Renders barcode using @isa/shared/barcode
  • Blocked state overlay for inactive cards
  • Fixed dimensions: 337×213px (based on Figma design)

AddCustomerCardComponent

Button and dialog for adding new customer cards.

Selector: crm-add-customer-card

Outputs:

  • cardAdded: void - Emitted when a card has been successfully added

Features:

  • Opens text input dialog for scanning or entering 8-digit card code
  • Validates card code input (required)
  • Calls CustomerCardsFacade.addCard() to add card
  • Shows success feedback dialog
  • Emits event for parent to reload cards

LockCustomerCardComponent

Component for locking/unlocking customer cards.

Selector: crm-lock-customer-card

Inputs:

  • isActive: boolean (required) - Whether the card is currently active or blocked
  • cardCode: string (required) - Card code to lock/unlock

Outputs:

  • cardLocked: void - Emitted when a card has been successfully locked or unlocked

Features:

  • Shows "Karte sperren" button for active cards
  • Shows "Karte entsperren" button for blocked cards
  • Handles loading state during API calls
  • Shows success/error feedback dialogs
  • Emits event for parent to reload cards

Usage

Integration in Parent Component

import { Component, signal } from '@angular/core';
import { CustomerLoyaltyCardsComponent } from '@isa/crm/feature/customer-loyalty-cards';

@Component({
  selector: 'app-customer-detail',
  imports: [CustomerLoyaltyCardsComponent],
  template: `
    <crm-customer-loyalty-cards
      [customerId]="customerId()"
      [tabId]="tabId()"
      (navigateToPraemienshop)="navigateToPraemienshop()"
      (cardUpdated)="handleCardUpdated()"
    />
  `
})
export class CustomerDetailComponent {
  customerId = signal(12345);
  tabId = signal(1);

  navigateToPraemienshop(): void {
    // Use autoTriggerContinueFn pattern to navigate with proper customer selection
    this.router.navigate(['/praemienshop'], {
      queryParams: { customerId: this.customerId() }
    });
  }

  handleCardUpdated(): void {
    // Reload cards and related data (e.g., transactions)
    this.customerService.reload(this.customerId());
  }
}

Required Provider Setup

The component requires CustomerBonusCardsResource to be provided:

import { CustomerBonusCardsResource } from '@isa/crm/data-access';

@Component({
  providers: [CustomerBonusCardsResource],
  // ...
})

This is typically provided at the feature route or parent component level to enable scoped resource management.

Dependencies

Internal Dependencies

Data Access:

  • @isa/crm/data-access - State management and API integration
    • CustomerBonusCardsResource - Resource API for loading bonus cards
    • CustomerCardsFacade - Facade for add/lock/unlock operations
    • BonusCardInfo - Type definition for bonus card data

UI Components:

  • @isa/ui/buttons - ButtonComponent, TextButtonComponent
  • @isa/ui/carousel - CarouselComponent for card scrolling
  • @isa/ui/dialog - Dialog services for add card and feedback
  • @isa/shared/barcode - BarcodeComponent for rendering card barcodes

Core:

  • @isa/core/logging - Logger factory for consistent logging

External Dependencies

  • @angular/core - Angular framework
  • @angular/forms - Form validation for card code input
  • @angular/cdk/coercion - Number input coercion
  • rxjs - For dialog result handling

State Management

This feature uses the Resource API pattern for state management:

// Reactive loading with automatic race condition prevention
effect(() => {
  const customerId = this.customerId();
  this.#bonusCardsResource.params({ customerId });
});

// Signals for state
readonly cards = this.#bonusCardsResource.resource.value;
readonly isLoading = this.#bonusCardsResource.resource.isLoading;
readonly error = this.#bonusCardsResource.resource.error;

This approach provides:

  • Automatic race condition handling (cancels previous requests)
  • Declarative resource management without RxJS subscriptions
  • Reactive updates when customerId changes
  • Built-in loading and error states

Testing

The library uses Vitest for testing with Angular Testing Utilities. Test files follow the pattern:

  • *.component.spec.ts - Component unit tests
  • Uses ComponentFixture and TestBed for component testing
  • Mocks for CustomerBonusCardsResource and CustomerCardsFacade

Run tests:

nx test crm-feature-customer-loyalty-cards

Design

Based on Figma designs with:

  • Card dimensions: 337×213px
  • Black header, white footer
  • Barcode sizing: 4.5rem height, 0.125rem width, 0.5rem margin
  • Carousel with horizontal scrolling and navigation arrows
  • Blocked cards display overlay and sort to end of carousel

Notes

  • Blocked cards sorting: Per Figma annotation, blocked cards are always sorted to the end of the carousel ("gesperrte Karte immer nach hinten")
  • Primary card points: Only the primary card's points are displayed in the summary
  • Navigation pattern: Uses output events instead of direct router navigation to support the autoTriggerContinueFn pattern for proper customer context
  • Card updates: All card mutation operations (add, lock, unlock) emit events for parent coordination to reload related data