# Les 4: TypeScript Fundamentals - Huiswerk ANTWOORDEN

Deze antwoordsleutel toont de volledige conversie van JavaScript naar TypeScript voor het huiswerk van Les 4. Voor elk bestand wordt een interface, type definitions en de complete TypeScript-implementatie getoond.

---

## 1. users.ts - Gebruikersbeheer

### Origineel JavaScript (users.js)
De originele JavaScript-versie bevat vijf functies voor gebruikersbeheer:
- `createUser()`: Maakt een nieuwe gebruiker aan met unieke ID en timestamp
- `findUserByEmail()`: Zoekt een gebruiker op emailadres
- `updateUser()`: Past gebruikerseigenschappen aan
- `filterActiveUsers()`: Filtert alleen actieve gebruikers
- `deactivateUser()`: Deactiveert een gebruiker

### Complete TypeScript Conversie

```typescript
// src/users.ts

/**
 * User interface - Definieert de structuur van een gebruikersobject
 */
interface User {
  id: string;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
  createdAt: Date;
}

/**
 * Maakt een nieuwe gebruiker aan
 * @param name - De naam van de gebruiker
 * @param email - Het emailadres van de gebruiker
 * @param age - De leeftijd van de gebruiker
 * @returns Een nieuw User-object
 */
function createUser(name: string, email: string, age: number): User {
  return {
    id: Math.random().toString(36).substr(2, 9),
    name,
    email,
    age,
    isActive: true,
    createdAt: new Date()
  };
}

/**
 * Zoekt een gebruiker op emailadres
 * @param users - Array van gebruikers
 * @param email - Het emailadres om naar te zoeken
 * @returns De gevonden User, of null als niet gevonden
 */
function findUserByEmail(users: User[], email: string): User | null {
  return users.find(user => user.email === email) || null;
}

/**
 * Werkt gebruikerseigenschappen bij
 * @param user - De originele gebruiker
 * @param updates - Gedeeltelijke updates (Partial<User>)
 * @returns De bijgewerkte User
 */
function updateUser(user: User, updates: Partial<User>): User {
  return { ...user, ...updates };
}

/**
 * Filtert alleen actieve gebruikers
 * @param users - Array van gebruikers
 * @returns Array van actieve gebruikers
 */
function filterActiveUsers(users: User[]): User[] {
  return users.filter(user => user.isActive);
}

/**
 * Deactiveert een gebruiker
 * @param user - De gebruiker om te deactiveren
 * @returns De gebruiker met isActive = false
 */
function deactivateUser(user: User): User {
  return { ...user, isActive: false };
}

export { createUser, findUserByEmail, updateUser, filterActiveUsers, deactivateUser };
export type { User };
```

### Sleutelbeslissingen bij TypeScript Conversie

1. **User Interface**: Een interface is gedefinieerd die alle properties van een gebruiker typeert. Dit zorgt voor type-safety en autocomplete.

2. **Functie Parameters**: Alle parameters zijn voorzien van expliciete TypeScript-typen:
   - `name: string`, `email: string`, `age: number` voor `createUser()`
   - `users: User[]` voor arrays van gebruikers

3. **Return Types**: Alle functies hebben expliciete return types:
   - `createUser()` retourneert `User`
   - `findUserByEmail()` retourneert `User | null` (union type voor null-safety)
   - `filterActiveUsers()` en `updateUser()` retourneren `User[]` respectievelijk `User`

4. **Partial<User> Type**: De `updateUser()` functie accepteert `Partial<User>` omdat niet alle eigenschappen hoeven te worden bijgewerkt. Dit is een built-in TypeScript utility type.

5. **Export**: Zowel functies als het User type worden geëxporteerd voor hergebruik in andere bestanden.

---

## 2. products.ts - Productbeheer

### Origineel JavaScript (products.js)
De JavaScript-versie bevat:
- `CATEGORIES`: Constante array met geldige categorieën
- `createProduct()`: Maakt een nieuw product aan met validatie
- `applyDiscount()`: Pas een kortingspercentage toe
- `getExpensiveProducts()`: Filtert producten op minimale prijs
- `formatPrice()`: Formatteert een prijs met valutasymbool
- `rateProduct()`: Voegt een rating toe (1-5)

### Complete TypeScript Conversie

```typescript
// src/products.ts

/**
 * Union type voor productcategorieën
 */
type Category = 'electronics' | 'clothing' | 'food' | 'books' | 'sports';

/**
 * Product interface - Definieert de structuur van een productobject
 */
interface Product {
  id: string;
  name: string;
  price: number;
  category: Category;
  description: string | null;
  inStock: boolean;
  rating: number | null;
}

/**
 * Geldige productcategorieën
 */
const CATEGORIES: Category[] = ['electronics', 'clothing', 'food', 'books', 'sports'];

/**
 * Maakt een nieuw product aan
 * @param name - Productnaam
 * @param price - Productprijs
 * @param category - Productcategorie (moet in CATEGORIES zitten)
 * @param description - Optionele productbeschrijving
 * @returns Een nieuw Product-object
 * @throws Error als categorie ongeldig is
 */
function createProduct(
  name: string,
  price: number,
  category: Category,
  description?: string
): Product {
  if (!CATEGORIES.includes(category)) {
    throw new Error(`Invalid category: ${category}`);
  }
  return {
    id: Math.random().toString(36).substr(2, 9),
    name,
    price,
    category,
    description: description || null,
    inStock: true,
    rating: null
  };
}

/**
 * Passe een kortingspercentage op een product toe
 * @param product - Het originele product
 * @param percentage - Kortingspercentage (0-100)
 * @returns Product met verlaagde prijs
 * @throws Error als percentage buiten bereik is
 */
function applyDiscount(product: Product, percentage: number): Product {
  if (percentage < 0 || percentage > 100) {
    throw new Error('Discount must be between 0 and 100');
  }
  const discountedPrice = product.price * (1 - percentage / 100);
  return { ...product, price: Math.round(discountedPrice * 100) / 100 };
}

/**
 * Filtert producten op minimale prijs
 * @param products - Array van producten
 * @param minPrice - Minimale prijs
 * @returns Array van producten met prijs >= minPrice
 */
function getExpensiveProducts(products: Product[], minPrice: number): Product[] {
  return products.filter(product => product.price >= minPrice);
}

/**
 * Formatteert een prijs met valutasymbool
 * @param price - Het prijsbedrag
 * @param currency - Valutacode ('EUR', 'USD', 'GBP')
 * @returns Geformateerde prijsstring (bijv. '€19.99')
 */
function formatPrice(price: number, currency: string): string {
  const currencySymbols: Record<string, string> = {
    EUR: '€',
    USD: '$',
    GBP: '£'
  };
  const symbol = currencySymbols[currency] || currency;
  return `${symbol}${price.toFixed(2)}`;
}

/**
 * Voegt een rating toe aan een product
 * @param product - Het originele product
 * @param rating - Rating waarde (1-5)
 * @returns Product met rating
 * @throws Error als rating buiten bereik is
 */
function rateProduct(product: Product, rating: number): Product {
  if (rating < 1 || rating > 5) {
    throw new Error('Rating must be between 1 and 5');
  }
  return { ...product, rating };
}

export { createProduct, applyDiscount, getExpensiveProducts, formatPrice, rateProduct, CATEGORIES };
export type { Product, Category };
```

### Sleutelbeslissingen bij TypeScript Conversie

1. **Category Union Type**: In plaats van een willekeurige string, gebruiken we een union type `type Category = 'electronics' | 'clothing' | 'food' | 'books' | 'sports'`. Dit voorkomt ongeldige categorieën en geeft betere autocomplete in IDE's.

2. **Product Interface**: Definieert alle eigenschappen met correcte typen:
   - `category: Category` in plaats van `string` (type-safe)
   - `description: string | null` en `rating: number | null` voor optionele waarden
   - `price: number` voor numerieke waarden

3. **CATEGORIES Type**: `const CATEGORIES: Category[]` zorgt ervoor dat de array alleen geldige categorieën bevat.

4. **Overloading Voorkomen**: De `createProduct()` functie accepteert `description?: string` (optionele parameter) in plaats van `description = null`. Dit is schoner en meer TypeScript-idiomatic.

5. **Record<string, string>**: Voor `currencySymbols` gebruiken we `Record<string, string>` in plaats van een gewone object, wat typezekerheid biedt.

6. **Validatie Types**: De functies valideren hun inputs en gooien TypeErrors met duidelijke berichten.

---

## 3. orders.ts - Orderbeheer

### Origineel JavaScript (orders.js)
De JavaScript-versie bevat:
- `createOrder()`: Maakt een nieuwe order aan
- `calculateTotal()`: Berekent de totale prijs
- `updateOrderStatus()`: Wijzigt orderstatus met validatie
- `getOrdersByUser()`: Filtert orders per gebruiker
- `getOrdersByStatus()`: Filtert orders per status

### Complete TypeScript Conversie

```typescript
// src/orders.ts

/**
 * Union type voor mogelijke orderstatussen
 */
type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';

/**
 * OrderItem interface - Representeert een product in een order
 */
interface OrderItem {
  productId: string;
  name: string;
  price: number;
}

/**
 * Order interface - Definieert de structuur van een orderobject
 */
interface Order {
  id: string;
  userId: string;
  products: OrderItem[];
  total: number;
  status: OrderStatus;
  createdAt: Date;
}

/**
 * Type voor geldige statustransities
 */
type ValidTransitions = Record<OrderStatus, OrderStatus[]>;

/**
 * Maakt een nieuwe order aan
 * @param user - Het User-object (moet minimaal id hebben)
 * @param products - Array van Product-objecten
 * @returns Een nieuw Order-object met status 'pending'
 */
function createOrder(
  user: { id: string },
  products: Array<{ id: string; name: string; price: number }>
): Order {
  return {
    id: Math.random().toString(36).substr(2, 9),
    userId: user.id,
    products: products.map(p => ({ productId: p.id, name: p.name, price: p.price })),
    total: calculateTotal(products),
    status: 'pending',
    createdAt: new Date()
  };
}

/**
 * Berekent het totaalbedrag van producten
 * @param products - Array van producten met price property
 * @returns Totaalbedrag
 */
function calculateTotal(products: Array<{ price: number }>): number {
  return products.reduce((sum, product) => sum + product.price, 0);
}

/**
 * Wijzigt de status van een order
 * @param order - De originele order
 * @param newStatus - De nieuwe status
 * @returns Order met bijgewerkte status
 * @throws Error als transactie ongeldig is
 */
function updateOrderStatus(order: Order, newStatus: OrderStatus): Order {
  const validTransitions: ValidTransitions = {
    pending: ['processing', 'cancelled'],
    processing: ['shipped', 'cancelled'],
    shipped: ['delivered'],
    delivered: [],
    cancelled: []
  };

  if (!validTransitions[order.status].includes(newStatus)) {
    throw new Error(`Cannot transition from ${order.status} to ${newStatus}`);
  }

  return { ...order, status: newStatus };
}

/**
 * Filtert orders van een specifieke gebruiker
 * @param orders - Array van orders
 * @param userId - De gebruikers-ID
 * @returns Array van orders van die gebruiker
 */
function getOrdersByUser(orders: Order[], userId: string): Order[] {
  return orders.filter(order => order.userId === userId);
}

/**
 * Filtert orders op status
 * @param orders - Array van orders
 * @param status - De filterstatus
 * @returns Array van orders met die status
 */
function getOrdersByStatus(orders: Order[], status: OrderStatus): Order[] {
  return orders.filter(order => order.status === status);
}

export { createOrder, calculateTotal, updateOrderStatus, getOrdersByUser, getOrdersByStatus };
export type { Order, OrderStatus, OrderItem };
```

### Sleutelbeslissingen bij TypeScript Conversie

1. **OrderStatus Union Type**: `type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled'` zorgt ervoor dat alleen geldige statussen kunnen worden gebruikt.

2. **OrderItem Interface**: Gescheiden interface voor orderitems maakt de code modulair en leesbaar.

3. **Order Interface**: Gebruikt het `OrderStatus` type voor de status property in plaats van `string`.

4. **Flexible Parameter Types**: `createOrder()` accepteert `user: { id: string }` in plaats van volledige User-interface. Dit maakt de functie flexibel en ontkoppeld van usersbeheer.

5. **ValidTransitions Type**: `Record<OrderStatus, OrderStatus[]>` type typeert de transities-object correct. Elke status mappen naar een array van geldige volgende statussen.

6. **Generieke Product Parameter**: `calculateTotal()` accepteert `Array<{ price: number }>` wat betekent dat het werkt met elk object dat een price-property heeft.

7. **Status Filtering**: `getOrdersByStatus()` accepteert `status: OrderStatus` waardoor alleen geldige statussen kunnen worden gefilterd.

---

## 4. utils.ts - Hulpfuncties

### Origineel JavaScript (utils.js)
De JavaScript-versie bevat:
- `formatDate()`: Formatteert een Date naar DD-MM-YYYY
- `generateId()`: Genereert een willekeurige ID-string
- `validateEmail()`: Valideert e-mailadressen met regex
- `sortBy()`: Generieke functie om items te sorteren
- `groupBy()`: Generieke functie om items te groeperen

### Complete TypeScript Conversie

```typescript
// src/utils.ts

/**
 * Formatteert een Date-object naar DD-MM-YYYY string
 * @param date - De Date-object
 * @returns Geformateerde datestring
 */
function formatDate(date: Date): string {
  const day = String(date.getDate()).padStart(2, '0');
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const year = date.getFullYear();
  return `${day}-${month}-${year}`;
}

/**
 * Genereert een willekeurige ID-string
 * @returns Unieke ID-string
 */
function generateId(): string {
  return Math.random().toString(36).substr(2, 9);
}

/**
 * Valideert of een string een geldig e-mailadres is
 * @param email - De email-string om te valideren
 * @returns true als geldig, false als ongeldig
 */
function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

/**
 * Sorteert een array van items op een specifieke property
 * Generieke functie met type parameters
 * @param items - Array van items om te sorteren
 * @param key - De property-naam om op te sorteren
 * @param direction - Sorteerrichting 'asc' of 'desc'
 * @returns Gesorteerde array (original wordt niet gewijzigd)
 */
function sortBy<T extends Record<string, any>>(
  items: T[],
  key: keyof T,
  direction: 'asc' | 'desc'
): T[] {
  return [...items].sort((a, b) => {
    const aValue = a[key];
    const bValue = b[key];

    if (direction === 'desc') {
      return aValue > bValue ? -1 : 1;
    }
    return aValue > bValue ? 1 : -1;
  });
}

/**
 * Groepeert items in een object op basis van een property
 * Generieke functie met type parameters
 * @param items - Array van items om te groeperen
 * @param key - De property-naam om op te groeperen
 * @returns Object waar keys de groepwaarden zijn en values arrays van items
 */
function groupBy<T extends Record<string, any>>(
  items: T[],
  key: keyof T
): Record<string | number | symbol, T[]> {
  return items.reduce((groups, item) => {
    const value = item[key];
    if (!groups[value]) {
      groups[value] = [];
    }
    groups[value].push(item);
    return groups;
  }, {} as Record<string | number | symbol, T[]>);
}

export { formatDate, generateId, validateEmail, sortBy, groupBy };
```

### Sleutelbeslissingen bij TypeScript Conversie

1. **Generieke Type Parameters**:
   - `<T extends Record<string, any>>` zorgt ervoor dat T elk object-type kan zijn met string-keys
   - Dit maakt `sortBy()` en `groupBy()` herbruikbaar voor alle object-arrays

2. **keyof T**: In plaats van een string voor de `key` parameter, gebruiken we `keyof T`. Dit ziet er in de IDE voor dat je alleen geldige properties van T kunt selecteren.

3. **Literal Union Type voor Direction**: `direction: 'asc' | 'desc'` in plaats van `string`, wat fouten voorkomt met ongeldige richtingen.

4. **Return Types**:
   - `sortBy()` retourneert `T[]` (dezelfde type als input)
   - `groupBy()` retourneert `Record<string | number | symbol, T[]>`

5. **Spread Operator Preserving**: In `sortBy()` gebruiken we `[...items]` om een kopie te maken. TypeScript begrijpt dat dit een `T[]` retourneert.

6. **Type Casting**: In `groupBy()` gebruiken we `as Record<string | number | symbol, T[]>` om TypeScript te helpen begrijpen dat we een lege Record initialiseren.

7. **Non-Mutating Approach**: Beide functies wijzigen de originele array niet, wat een best practice is voor pure functions.

---

## Samenvattend: TypeScript Conversie Principes

### 1. **Interfaces voor Data Structures**
- Elk belangrijk data-object krijgt een interface (User, Product, Order)
- Interfaces documenteren de verwachte structuur en verbeteren IDE support

### 2. **Union Types voor Enumeraties**
- In plaats van willekeurige strings, gebruiken we union types:
  - `type Category = 'electronics' | 'clothing' | ...`
  - `type OrderStatus = 'pending' | 'processing' | ...`
- Dit voorkomt fouten en verbetert type-safety

### 3. **Generieke Types**
- Voor herbruikbare functies als `sortBy()` en `groupBy()` gebruiken we generieke parameters
- `<T extends Record<string, any>>` zorgt voor type-safety terwijl flexibiliteit behouden blijft

### 4. **Null/Undefined Handling**
- Union types met null/undefined: `string | null`, `number | null`
- Dit dwingt expliciete handling van lege waarden

### 5. **Parameter Type Hints**
- Alle parameters hebben expliciete TypeScript-typen
- Optionele parameters gebruiken `?` of `...?: Type` syntax

### 6. **Return Type Annotations**
- Alle functies hebben expliciete return type annotations
- Dit helpt compile-time fouten opsporen en documenteert intent

### 7. **keyof en Generieke Constraints**
- `keyof T` zorgt ervoor dat je alleen geldige properties kunt selecteren
- `extends Record<string, any>` constrained het type naar objecten

### 8. **Typing van Object Methods**
- `Partial<T>` voor optionele updates
- `Record<K, V>` voor type-safe object literals
- `Pick<T, Keys>` en `Omit<T, Keys>` voor subset typen

---

## Test Compatibiliteit

Alle bovenstaande TypeScript conversies zijn ontworpen om perfect te werken met de bestaande test suite:

- **users.test.ts**: Tests gebruiken `User` interface
- **products.test.ts**: Tests gebruiken `Product` interface en `Category` type
- **orders.test.ts**: Tests gebruiken `Order`, `OrderStatus` en gerelateerde typen
- **utils.test.ts**: Tests gebruiken generieke functies met impliciete type-inferentie

De TypeScript-versies behouden exact dezelfde logic en runtime-gedrag als de originele JavaScript-versies, terwijl ze de voordelen van type-safety bieden.
