# @isa/checkout/shared/product-info

A comprehensive collection of presentation components for displaying product information, destination details, and stock availability in checkout and rewards workflows.

## Overview

The Product Info library provides three specialized components for rendering product-related information in the checkout domain. These components handle the visual presentation layer for reward redemptions, delivery/pickup destinations, and real-time stock availability. Each component is designed as a standalone Angular component with signal-based inputs and optimized change detection.

## Table of Contents

- [Features](#features)
- [Quick Start](#quick-start)
- [Components](#components)
  - [ProductInfoRedemptionComponent](#productinforedemptioncomponent)
  - [DestinationInfoComponent](#destinationinfocomponent)
  - [StockInfoComponent](#stockinfocomponent)
- [Type Definitions](#type-definitions)
- [Usage Examples](#usage-examples)
- [Styling and Customization](#styling-and-customization)
- [Architecture Notes](#architecture-notes)
- [Testing](#testing)
- [Dependencies](#dependencies)

## Features

- **Product redemption display** - Formatted product information with reward points
- **Destination visualization** - Dynamic display of shipping/pickup/in-store destinations
- **Real-time stock info** - Asynchronous stock availability with loading states
- **Flexible layouts** - Horizontal and vertical orientation support
- **Signal-based reactivity** - Modern Angular signals for optimal performance
- **Resource integration** - Angular Resource API for efficient data fetching
- **E2E test support** - Comprehensive `data-what` attributes for automated testing
- **Tailwind styling** - ISA design system integration with custom utilities
- **OnPush change detection** - Optimized rendering performance
- **Type-safe inputs** - Strict TypeScript types with discriminated unions

## Quick Start

### 1. Import Components

```typescript
import { Component } from '@angular/core';
import {
  ProductInfoRedemptionComponent,
  DestinationInfoComponent,
  StockInfoComponent,
  type ProductInfoItem,
  type StockInfoItem,
} from '@isa/checkout/shared/product-info';

@Component({
  selector: 'app-checkout-summary',
  template: '...',
  imports: [
    ProductInfoRedemptionComponent,
    DestinationInfoComponent,
    StockInfoComponent,
  ],
})
export class CheckoutSummaryComponent {
  // Component logic...
}
```

### 2. Basic Product Info Display

```typescript
@Component({
  template: `
    <checkout-product-info-redemption
      [item]="productItem()"
      [orientation]="'vertical'"
    />
  `,
  imports: [ProductInfoRedemptionComponent],
})
export class RewardItemComponent {
  productItem = signal<ProductInfoItem>({
    product: {
      ean: '9783498007706',
      name: 'Die Assistentin',
      contributors: 'Wahl, Caroline',
      format: 'TB',
      formatDetail: 'Taschenbuch (Kartoniert)',
      manufacturer: 'Rowohlt Verlag',
      publicationDate: '2023-01-15',
    },
    redemptionPoints: 150,
  });
}
```

### 3. Destination Display

```typescript
@Component({
  template: `
    <checkout-destination-info
      [shoppingCartItem]="cartItem()"
      [underline]="true"
    />
  `,
  imports: [DestinationInfoComponent],
})
export class CartItemComponent {
  cartItem = signal({
    availability: {
      estimatedDelivery: {
        start: '2024-06-10T00:00:00+02:00',
        stop: '2024-06-12T00:00:00+02:00',
      },
    },
    destination: {
      data: {
        target: ShippingTarget.Delivery,
      },
    },
    features: {
      orderType: 'Versand',
    },
  });
}
```

### 4. Stock Information Display

```typescript
@Component({
  template: `
    <checkout-stock-info [item]="stockItem()" />
  `,
  imports: [StockInfoComponent],
})
export class ProductStockComponent {
  stockItem = signal<StockInfoItem>({
    id: 123456,
    catalogAvailability: {
      ssc: '999',
      sscText: 'Lieferbar in 1-3 Werktagen',
    },
  });
}
```

## Components

### ProductInfoRedemptionComponent

A presentation component for displaying product information with redemption points in reward workflows.

#### Selector

```html
<checkout-product-info-redemption />
```

#### Inputs

| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `item` | `ProductInfoItem` | - | ✓ | Product data with redemption points or loyalty information |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | ✗ | Layout orientation |

#### Outputs

None - presentation component only.

#### Computed Properties

| Name | Type | Description |
|------|------|-------------|
| `points` | `Signal<number>` | Calculated reward points from redemptionPoints or loyalty.value |

#### Host Bindings

- **`class`**: Dynamically bound to `orientation()` for CSS styling

#### Template Features

- **Product Image**: Clickable product image with `sharedProductRouterLink` directive
- **Contributors**: Bold product author/artist display
- **Product Name**: Responsive typography (body-2 horizontal, subtitle-1 vertical)
- **Reward Points**: Combined display of points value and "Lesepunkte" label
- **Format Information**: Delegated to `shared-product-format` component
- **Metadata**: Manufacturer, EAN, and publication date
- **E2E Attributes**: `data-what="product-image"` for automated testing

#### Example

```typescript
@Component({
  template: `
    <checkout-product-info-redemption
      [item]="rewardItem()"
      [orientation]="'horizontal'"
    />
  `,
})
export class RewardCardComponent {
  rewardItem = signal<ProductInfoItem>({
    product: {
      ean: '9783498007706',
      name: 'Die Assistentin',
      contributors: 'Wahl, Caroline',
      format: 'TB',
      formatDetail: 'Taschenbuch (Kartoniert)',
      manufacturer: 'Rowohlt Verlag',
      publicationDate: '2023-01-15',
    },
    redemptionPoints: 150,
  });
}
```

### DestinationInfoComponent

Displays shipping destination, branch information, or estimated delivery dates based on order type.

#### Selector

```html
<checkout-destination-info />
```

#### Inputs

| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `shoppingCartItem` | `Pick<ShoppingCartItem, 'availability' \| 'destination' \| 'features'>` | - | ✓ | Shopping cart item with destination data |
| `underline` | `boolean` | `false` | ✗ | Add underline styling to destination header |

#### Outputs

None - presentation component only.

#### Computed Properties

| Name | Type | Description |
|------|------|-------------|
| `orderType` | `Signal<OrderType>` | Extracted order type from cart item features |
| `destinationIcon` | `Signal<string>` | Icon name based on order type (delivery, pickup, in-store) |
| `displayAddress` | `Signal<boolean>` | Whether to show address (true for InStore, Pickup, B2B) |
| `branchContainer` | `Signal<EntityReferenceContainer \| undefined>` | Branch reference from destination data |
| `branch` | `LinkedSignal<Branch \| undefined>` | Resolved branch data from resource or container |
| `name` | `Signal<string>` | Recipient name (customer or branch name) |
| `address` | `Signal<Address \| undefined>` | Destination address (shipping or branch) |
| `estimatedDelivery` | `Signal<{ start: string; stop: string \| null } \| null>` | Calculated delivery date range |

#### Effects

- **`branchChange`**: Automatically updates `BranchResource` params when branch container changes

#### Injected Resources

- **`BranchResource`**: Fetches branch details by ID
- **`SelectedCustomerShippingAddressResource`**: Fetches customer shipping address

#### Order Type Behavior

| Order Type | Icon | Display Mode | Data Source |
|------------|------|--------------|-------------|
| **Versand** (Delivery) | `isaDeliveryVersand` | Estimated delivery dates | Availability data |
| **Abholung** (Pickup) | `isaDeliveryRuecklage2` | Branch name + address | Destination.targetBranch |
| **Rücklage** (InStore) | `isaDeliveryRuecklage1` | Branch name + address | Destination.targetBranch |
| **B2B-Versand** | `isaDeliveryVersand` | Customer name + shipping address | ShippingAddressResource |
| **DIG-Versand** | `isaDeliveryVersand` | Customer name + shipping address | ShippingAddressResource |

#### Example

```typescript
@Component({
  template: `
    <checkout-destination-info
      [shoppingCartItem]="cartItem()"
      [underline]="true"
    />
  `,
})
export class ShippingInfoComponent {
  cartItem = signal({
    availability: {
      estimatedDelivery: {
        start: '2024-06-10T00:00:00+02:00',
        stop: '2024-06-12T00:00:00+02:00',
      },
    },
    destination: {
      data: {
        target: ShippingTarget.Branch,
        targetBranch: {
          id: 42,
          data: {
            name: 'Hauptfiliale München',
            address: {
              street: 'Marienplatz',
              streetNumber: '1',
              zipCode: '80331',
              city: 'München',
            },
          },
        },
      },
    },
    features: {
      orderType: 'Abholung',
    },
  });
}
```

### StockInfoComponent

Displays real-time stock availability with asynchronous loading states.

#### Selector

```html
<checkout-stock-info />
```

#### Inputs

| Name | Type | Default | Required | Description |
|------|------|---------|----------|-------------|
| `item` | `StockInfoItem` | - | ✓ | Item with catalog availability information |

#### Outputs

None - presentation component only.

#### Computed Properties

| Name | Type | Description |
|------|------|-------------|
| `itemId` | `Signal<number>` | Extracted item ID from input |
| `inStock` | `Signal<number>` | Current stock quantity (0 if not loaded) |
| `loading` | `Signal<boolean>` | Loading state from resource |

#### Public Properties

| Name | Type | Description |
|------|------|-------------|
| `stockInfoResource` | `ResourceRef<StockInfo>` | Exposed resource reference for external access |

#### Exported As

```html
<checkout-stock-info #stockInfo="stockInfo" />
```

Access resource methods:
```typescript
@ViewChild('stockInfo', { read: StockInfoComponent })
stockInfoRef!: StockInfoComponent;

reloadStock() {
  this.stockInfoRef.stockInfoResource.reload();
}
```

#### Resource Integration

Uses `StockInfoResource` from `@isa/remission/data-access` with automatic parameter updates:

```typescript
readonly stockInfoResource = this.#stockInfoResource.resource(
  computed(() => ({ itemId: this.itemId() }))
);
```

#### Loading State Handling

The component renders a skeleton loader during data fetching:

```html
@if (loading()) {
  <ui-skeleton-loader class="w-8 h-5 inline-block"></ui-skeleton-loader>
} @else {
  {{ inStock() }}x
}
```

#### Example

```typescript
@Component({
  template: `
    <checkout-stock-info
      #stockInfo="stockInfo"
      [item]="productItem()"
    />
    <button (click)="stockInfo.stockInfoResource.reload()">
      Refresh Stock
    </button>
  `,
})
export class ProductAvailabilityComponent {
  productItem = signal<StockInfoItem>({
    id: 123456,
    catalogAvailability: {
      ssc: '10',
      sscText: 'Sofort lieferbar',
    },
  });
}
```

## Type Definitions

### ProductInfoItem

Discriminated union type supporting two product sources:

```typescript
type ProductInfoItem =
  | {
      product: Pick<
        CatProduct,
        'ean' | 'name' | 'contributors' | 'format' |
        'formatDetail' | 'manufacturer' | 'publicationDate'
      >;
      redemptionPoints?: number;
    }
  | {
      product: Pick<
        CheckoutProduct,
        'ean' | 'name' | 'contributors' | 'format' |
        'formatDetail' | 'manufacturer' | 'publicationDate'
      >;
      loyalty: Pick<Loyalty, 'value'>;
    };
```

**Catalog Product Variant:**
- Uses `@isa/catalogue/data-access` Product type
- Optional `redemptionPoints` field
- Typical use: Reward catalog browsing

**Checkout Product Variant:**
- Uses `@isa/checkout/data-access` Product type
- Required `loyalty` object with `value` field
- Typical use: Shopping cart redemption items

**Type Discrimination:**
```typescript
const item: ProductInfoItem = {...};

if ('redemptionPoints' in item) {
  // Catalog product variant
  const points = item.redemptionPoints ?? 0;
}

if ('loyalty' in item) {
  // Checkout product variant
  const points = item.loyalty.value ?? 0;
}
```

### StockInfoItem

Simple type for stock availability display:

```typescript
type StockInfoItem = {
  id: Item['id'];  // Product item ID
  catalogAvailability: Pick<Item['catalogAvailability'], 'ssc' | 'sscText'>;
};
```

**Fields:**
- `id`: Numeric item identifier for stock lookup
- `catalogAvailability.ssc`: Stock status code (e.g., "10", "999")
- `catalogAvailability.sscText`: Human-readable status description

## Usage Examples

### Horizontal Product Info Layout

```typescript
@Component({
  template: `
    <checkout-product-info-redemption
      [item]="product()"
      [orientation]="'horizontal'"
      class="border-b pb-4"
    />
  `,
  imports: [ProductInfoRedemptionComponent],
})
export class HorizontalRewardComponent {
  product = signal<ProductInfoItem>({
    product: {
      ean: '9783442488391',
      name: 'Das Labyrinth des Fauns',
      contributors: 'del Toro, Guillermo; Funke, Cornelia',
      format: 'HC',
      formatDetail: 'Hardcover (gebunden)',
      manufacturer: 'Goldmann Verlag',
      publicationDate: '2019-07-01',
    },
    redemptionPoints: 200,
  });
}
```

### Multiple Destination Types

```typescript
@Component({
  template: `
    @for (item of cartItems(); track item.id) {
      <checkout-destination-info
        [shoppingCartItem]="item"
        [underline]="true"
        class="mb-4"
      />
    }
  `,
  imports: [DestinationInfoComponent],
})
export class MultiDestinationComponent {
  cartItems = signal([
    {
      id: 1,
      availability: {
        estimatedDelivery: {
          start: '2024-06-15T00:00:00+02:00',
          stop: null,
        },
      },
      destination: { data: { target: ShippingTarget.Delivery } },
      features: { orderType: 'Versand' },
    },
    {
      id: 2,
      destination: {
        data: {
          target: ShippingTarget.Branch,
          targetBranch: {
            id: 42,
            data: {
              name: 'Filiale Hamburg',
              address: {
                street: 'Mönckebergstraße',
                streetNumber: '10',
                zipCode: '20095',
                city: 'Hamburg',
              },
            },
          },
        },
      },
      features: { orderType: 'Abholung' },
    },
  ]);
}
```

### Stock Info with Manual Reload

```typescript
@Component({
  template: `
    <checkout-stock-info
      #stockRef="stockInfo"
      [item]="productItem()"
    />

    <button
      (click)="refreshStock()"
      [disabled]="stockRef.loading()"
      class="mt-2 px-4 py-2 bg-blue-500 text-white rounded"
    >
      {{ stockRef.loading() ? 'Loading...' : 'Refresh Stock' }}
    </button>
  `,
  imports: [StockInfoComponent],
})
export class StockRefreshComponent {
  @ViewChild('stockRef', { read: StockInfoComponent })
  stockInfoRef!: StockInfoComponent;

  productItem = signal<StockInfoItem>({
    id: 789012,
    catalogAvailability: {
      ssc: '20',
      sscText: 'Lieferbar in 5-7 Werktagen',
    },
  });

  refreshStock() {
    this.stockInfoRef.stockInfoResource.reload();
  }
}
```

### Combined Product Display

```typescript
@Component({
  template: `
    <div class="grid grid-cols-[2fr,1fr] gap-6 p-4 border rounded">
      <!-- Left: Product Info -->
      <checkout-product-info-redemption
        [item]="productData()"
        [orientation]="'vertical'"
      />

      <!-- Right: Stock & Destination -->
      <div class="flex flex-col gap-4">
        <checkout-stock-info [item]="stockData()" />
        <checkout-destination-info
          [shoppingCartItem]="cartData()"
          [underline]="false"
        />
      </div>
    </div>
  `,
  imports: [
    ProductInfoRedemptionComponent,
    StockInfoComponent,
    DestinationInfoComponent,
  ],
})
export class ProductCardComponent {
  productData = signal<ProductInfoItem>({
    product: {
      ean: '9783453441378',
      name: 'Der Schwarm',
      contributors: 'Schätzing, Frank',
      format: 'TB',
      formatDetail: 'Taschenbuch (Kartoniert)',
      manufacturer: 'Heyne Verlag',
      publicationDate: '2005-08-01',
    },
    redemptionPoints: 300,
  });

  stockData = signal<StockInfoItem>({
    id: 456789,
    catalogAvailability: {
      ssc: '10',
      sscText: 'Sofort lieferbar',
    },
  });

  cartData = signal({
    availability: {
      estimatedShippingDate: '2024-06-08T00:00:00+02:00',
    },
    destination: { data: { target: ShippingTarget.Delivery } },
    features: { orderType: 'Versand' },
  });
}
```

### Loyalty-Based Redemption

```typescript
@Component({
  template: `
    <checkout-product-info-redemption
      [item]="loyaltyProduct()"
      [orientation]="'horizontal'"
    />
  `,
  imports: [ProductInfoRedemptionComponent],
})
export class LoyaltyRedemptionComponent {
  loyaltyProduct = signal<ProductInfoItem>({
    product: {
      ean: '9783423140942',
      name: 'Der Vorleser',
      contributors: 'Schlink, Bernhard',
      format: 'TB',
      formatDetail: 'Taschenbuch (Kartoniert)',
      manufacturer: 'dtv Verlag',
      publicationDate: '1997-09-01',
    },
    loyalty: {
      value: 250,  // Loyalty points instead of redemptionPoints
    },
  });
}
```

### Conditional Rendering Based on Order Type

```typescript
@Component({
  template: `
    <checkout-destination-info
      [shoppingCartItem]="cartItem()"
      [underline]="shouldUnderline()"
    />

    @if (isDelivery()) {
      <p class="mt-2 text-sm text-neutral-600">
        Standard shipping applies (3-5 business days)
      </p>
    }

    @if (isPickup() || isInStore()) {
      <p class="mt-2 text-sm text-neutral-600">
        Please bring a valid ID when picking up
      </p>
    }
  `,
  imports: [DestinationInfoComponent],
})
export class ConditionalDestinationComponent {
  cartItem = signal({
    availability: {
      estimatedDelivery: {
        start: '2024-06-20T00:00:00+02:00',
        stop: '2024-06-22T00:00:00+02:00',
      },
    },
    destination: { data: { target: ShippingTarget.Delivery } },
    features: { orderType: 'Versand' },
  });

  orderType = computed(() =>
    getOrderTypeFeature(this.cartItem().features)
  );

  isDelivery = computed(() => this.orderType() === OrderType.Delivery);
  isPickup = computed(() => this.orderType() === OrderType.Pickup);
  isInStore = computed(() => this.orderType() === OrderType.InStore);

  shouldUnderline = computed(() => !this.isDelivery());
}
```

## Styling and Customization

### Tailwind CSS Integration

All components use ISA design system utilities with Tailwind CSS:

#### Typography Classes
- `isa-text-body-2-bold`: Bold body text (14px)
- `isa-text-body-2-regular`: Regular body text (14px)
- `isa-text-subtitle-1-regular`: Larger subtitle text (16px)

#### Color Classes
- `text-neutral-900`: Primary text color
- `text-neutral-600`: Secondary text color
- `text-isa-secondary-900`: Secondary accent color

#### Layout Classes
- `grid`, `flex`: Flexbox and grid layouts
- `gap-2`, `gap-4`, `gap-6`: Spacing utilities
- `items-center`, `items-start`: Alignment utilities

### Component-Specific Styles

#### ProductInfoRedemptionComponent

**Host Styles:**
```css
:host {
  @apply grid gap-6 text-neutral-900;
}

:host.horizontal {
  @apply grid-cols-[1fr,minmax(0,25rem)];
}

:host.vertical {
  @apply grid-flow-row;
}
```

**Custom Classes:**
```css
.checkout-product-info-redemption__image {
  @apply w-14;  /* 56px fixed width */
}

shared-product-format span {
  @apply isa-text-body-2-bold text-isa-secondary-900;
}
```

**Layout Variations:**
- **Horizontal**: Two-column grid with 1fr left, max-width 25rem right
- **Vertical**: Single-column flow with left margin on metadata (ml-20)

#### DestinationInfoComponent

**Host Styles:**
```css
:host {
  @apply flex flex-col items-start gap-2 flex-grow;
}
```

**Custom Classes:**
```css
.address-container {
  @apply line-clamp-2 break-words text-ellipsis;
}

.underline {
  /* Applied dynamically via [class.underline] binding */
}
```

**Icon Sizing:**
- All icons: `1.5rem` (24px)
- Color: `text-neutral-900`

#### StockInfoComponent

**Host Styles:**
```css
:host {
  @apply grid grid-flow-row gap-2;
}
```

**Grid Layout:**
```css
.grid.grid-cols-\[auto\,1fr\] {
  /* Icon column: auto-sized */
  /* Content column: fills remaining space */
}
```

### Customization Examples

#### Custom Product Image Size

```typescript
@Component({
  template: `
    <checkout-product-info-redemption
      [item]="product()"
      class="custom-image-size"
    />
  `,
  styles: [`
    ::ng-deep .custom-image-size .checkout-product-info-redemption__image {
      @apply w-20 h-28;  /* Custom dimensions */
    }
  `],
})
export class CustomImageComponent { }
```

#### Custom Destination Styling

```typescript
@Component({
  template: `
    <checkout-destination-info
      [shoppingCartItem]="item()"
      class="custom-destination"
    />
  `,
  styles: [`
    .custom-destination {
      @apply bg-neutral-100 p-4 rounded-lg;
    }

    .custom-destination .address-container {
      @apply line-clamp-3;  /* Show 3 lines instead of 2 */
    }
  `],
})
export class CustomDestinationComponent { }
```

#### Custom Stock Info Colors

```typescript
@Component({
  template: `
    <checkout-stock-info
      [item]="stockItem()"
      class="stock-highlight"
    />
  `,
  styles: [`
    .stock-highlight {
      @apply border-l-4 border-green-500 pl-4;
    }

    ::ng-deep .stock-highlight .isa-text-body-2-bold {
      @apply text-green-700;
    }
  `],
})
export class CustomStockComponent { }
```

## Architecture Notes

### Design Patterns

#### 1. Presentation Components
All three components follow the **Presentation Component** pattern:
- No business logic or state management
- Pure transformation of input data to visual output
- Signal-based inputs for optimal change detection
- OnPush change detection for performance

#### 2. Resource Integration
`DestinationInfoComponent` and `StockInfoComponent` demonstrate the **Resource Provider** pattern:
- Self-contained resource management
- Automatic parameter updates via computed signals
- Loading state handling
- Error boundary support

#### 3. Type Safety
Components use **Discriminated Unions** for flexible input types:
- `ProductInfoItem` supports both catalog and checkout products
- Type narrowing via `'redemptionPoints' in item` checks
- Prevents runtime type errors

### Component Responsibilities

#### ProductInfoRedemptionComponent
**Responsibility**: Display product metadata and reward points
**Dependencies**: Product image/router directives, product format component
**State**: Stateless (pure transformation)
**Integration Points**: Product detail routing, image service

#### DestinationInfoComponent
**Responsibility**: Display destination with order-type-specific formatting
**Dependencies**: Branch and shipping address resources
**State**: Resource-managed (branch, shipping address)
**Integration Points**: CRM shipping addresses, branch management

#### StockInfoComponent
**Responsibility**: Display real-time stock availability
**Dependencies**: Stock info resource, skeleton loader
**State**: Resource-managed (stock data)
**Integration Points**: Inventory API, remission stock service

### Known Architectural Considerations

#### 1. Resource Provider Scope (Low Priority)

**Current State:**
- `DestinationInfoComponent` and `StockInfoComponent` provide resources at component level
- Each component instance creates its own resource instances

**Impact:**
- Multiple instances fetch data independently
- No resource sharing between sibling components

**Consideration:**
- For list views with many instances, consider parent-level resource provision
- Example: Product list with stock info for each item

**Optimization Strategy:**
```typescript
@Component({
  template: `
    @for (item of items(); track item.id) {
      <checkout-stock-info [item]="item" />
    }
  `,
  providers: [StockInfoResource],  // Shared resource for all children
})
export class ProductListComponent { }
```

#### 2. Destination Info Complexity (Medium Priority)

**Current State:**
- Component handles 5 different order types
- Complex conditional logic for address vs. delivery date display
- Multiple resource dependencies (branch, shipping address)

**Consideration:**
- Consider decomposition into smaller components:
  - `DestinationDeliveryComponent` (Versand, DIG-Versand, B2B-Versand)
  - `DestinationBranchComponent` (Abholung, Rücklage)
- Benefits: Simpler logic, fewer dependencies per component

**Current Justification:**
- Single component ensures consistent icon/header rendering
- Shared logic reduces code duplication
- Complexity is well-encapsulated

#### 3. Product Info Type Union (Low Priority)

**Current State:**
- Discriminated union supports both catalog and checkout products
- Runtime type checking required (`'redemptionPoints' in item`)

**Alternative:**
- Two separate components: `ProductInfoCatalog`, `ProductInfoCheckout`
- Eliminates runtime type checks
- More explicit component selection

**Current Justification:**
- Single component provides consistent rendering
- Type discrimination is lightweight
- Simplifies template usage

### Performance Considerations

#### Change Detection Optimization
All components use `OnPush` change detection:
- Updates only when signal inputs change
- No unnecessary re-renders
- Optimal for large lists

#### Resource Request Deduplication
Resource APIs automatically deduplicate concurrent requests:
```typescript
// Multiple components requesting same stock data
<checkout-stock-info [item]="{ id: 123, ... }" />
<checkout-stock-info [item]="{ id: 123, ... }" />
// Only one API request is made
```

#### Skeleton Loading States
`StockInfoComponent` shows skeleton loaders during data fetching:
- Prevents layout shift
- Better perceived performance
- User feedback during async operations

### Future Enhancements

1. **Virtualization Support**: Optimize for large product lists with virtual scrolling
2. **Caching Strategy**: Implement resource-level caching for frequently accessed data
3. **Offline Support**: Handle network failures gracefully with cached data
4. **Accessibility**: Add ARIA labels and screen reader support
5. **Analytics Integration**: Track user interactions with product info components

## Testing

The library uses **Vitest** with **Angular Testing Utilities** for testing.

### Running Tests

```bash
# Run tests for this library
npx nx test checkout-shared-product-info --skip-nx-cache

# Run tests with coverage
npx nx test checkout-shared-product-info --code-coverage --skip-nx-cache

# Run tests in watch mode
npx nx test checkout-shared-product-info --watch
```

### Test Structure

The library includes unit tests covering:

- **Component creation** - Validates successful component instantiation
- **Input bindings** - Tests signal input reactivity
- **Computed properties** - Validates derived signal calculations
- **Resource integration** - Tests resource parameter updates
- **Loading states** - Validates skeleton loader display
- **E2E attributes** - Ensures `data-what` attributes are present

### Example Test: StockInfoComponent

```typescript
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { describe, it, expect, beforeEach } from 'vitest';
import { StockInfoComponent } from './stock-info.component';
import { RemissionStockService } from '@isa/remission/data-access';

describe('StockInfoComponent', () => {
  let component: StockInfoComponent;
  let fixture: ComponentFixture<StockInfoComponent>;
  let mockStockService: any;

  beforeEach(() => {
    mockStockService = {
      fetchStockInfos: () => Promise.resolve([]),
    };

    TestBed.configureTestingModule({
      imports: [StockInfoComponent],
      providers: [
        { provide: RemissionStockService, useValue: mockStockService },
      ],
    });

    fixture = TestBed.createComponent(StockInfoComponent);
    component = fixture.componentInstance;
    fixture.componentRef.setInput('item', {
      id: 123,
      catalogAvailability: { ssc: '10', sscText: 'Available' },
    });
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should have item input', () => {
    expect(component.item()).toEqual({
      id: 123,
      catalogAvailability: { ssc: '10', sscText: 'Available' },
    });
  });

  it('should initialize with zero inStock when no data loaded', () => {
    expect(component.inStock()).toBe(0);
  });

  it('should have stockInfoResource ResourceRef defined', () => {
    expect(component.stockInfoResource).toBeDefined();
    expect(component.stockInfoResource.value).toBeDefined();
    expect(component.stockInfoResource.isLoading).toBeDefined();
    expect(component.stockInfoResource.reload).toBeDefined();
  });
});
```

### Testing Best Practices

#### 1. Signal Input Testing

Always use `fixture.componentRef.setInput()` for signal inputs:

```typescript
// Correct - for signal inputs
fixture.componentRef.setInput('item', mockItem);
fixture.detectChanges();

// Incorrect - for signal inputs (won't work)
component.item = signal(mockItem);
```

#### 2. Resource Mocking

Mock resource dependencies at the service level:

```typescript
const mockStockService = {
  fetchStockInfos: vi.fn().mockResolvedValue([
    { itemId: 123, inStock: 5 }
  ]),
};

TestBed.configureTestingModule({
  providers: [
    { provide: RemissionStockService, useValue: mockStockService },
  ],
});
```

#### 3. Computed Signal Testing

Test computed signals after input changes:

```typescript
it('should calculate points from redemptionPoints', () => {
  fixture.componentRef.setInput('item', {
    product: { /* ... */ },
    redemptionPoints: 150,
  });
  fixture.detectChanges();

  expect(component.points()).toBe(150);
});

it('should calculate points from loyalty', () => {
  fixture.componentRef.setInput('item', {
    product: { /* ... */ },
    loyalty: { value: 200 },
  });
  fixture.detectChanges();

  expect(component.points()).toBe(200);
});
```

#### 4. E2E Attribute Verification

Verify E2E attributes in template:

```typescript
it('should have data-what attribute on product image', () => {
  fixture.detectChanges();
  const image = fixture.nativeElement.querySelector('img');
  expect(image.getAttribute('data-what')).toBe('product-image');
});
```

### Storybook Integration

Each component has comprehensive Storybook stories for visual testing:

#### Product Info Stories

```typescript
// apps/isa-app/stories/checkout/shared/product-info/product-info-redemption.stories.ts
export const Primary: Story = {
  args: {
    item: {
      product: {
        ean: '9783498007706',
        name: 'Die Assistentin',
        contributors: 'Wahl, Caroline',
        // ...
      },
      redemptionPoints: 100,
    },
    orientation: 'vertical',
  },
};
```

#### Running Storybook

```bash
npm run storybook
# Navigate to: checkout/shared/product-info/*
```

### CI/CD Testing

The library is included in CI test runs:

```bash
# CI command (used in pipelines)
npm run ci
# Or: npx nx run-many -t test -c ci
```

## Dependencies

### Required Libraries

#### Angular Core
- `@angular/core` - Angular framework
- `@angular/common` - Common Angular directives (DatePipe)
- `@angular/cdk/coercion` - Coercion utilities (boolean inputs)

#### Domain Libraries
- `@isa/catalogue/data-access` - Catalog product types
- `@isa/checkout/data-access` - Checkout product types, order types, shopping cart
- `@isa/crm/data-access` - Customer shipping address resource
- `@isa/remission/data-access` - Stock service and branch resource

#### Shared Components
- `@isa/shared/product-image` - Product image directive
- `@isa/shared/product-router-link` - Product routing directive
- `@isa/shared/product-format` - Product format display component
- `@isa/shared/address` - Inline address component

#### UI Components
- `@isa/ui/skeleton-loader` - Loading skeleton component
- `@ng-icons/core` - Icon component
- `@isa/icons` - ISA icon set (isaFiliale, isaDeliveryVersand, etc.)

#### Generated API
- `@generated/swagger/inventory-api` - Stock info DTO types

### Development Dependencies

- `vitest` - Testing framework
- `@angular/platform-browser-dynamic` - Testing utilities
- `@storybook/angular` - Storybook integration

### Path Alias

Import from: `@isa/checkout/shared/product-info`

### Peer Dependencies

These must be provided by the consuming application:
- Angular 20.1.2+
- RxJS 7.5+
- Tailwind CSS with ISA design system configuration

## License

Internal ISA Frontend library - not for external distribution.
