Medowar 2 luni în urmă
comite
bdc4516a14
17 a modificat fișierele cu 2348 adăugiri și 0 ștergeri
  1. 110 0
      admin/index.php
  2. 70 0
      admin/login.php
  3. 350 0
      admin/products.php
  4. 203 0
      admin/reservations.php
  5. 370 0
      assets/css/style.css
  6. 129 0
      cart.php
  7. 146 0
      checkout.php
  8. 38 0
      config.php
  9. 3 0
      data/.htaccess
  10. 98 0
      data/products.json
  11. 3 0
      data/reservations.json
  12. 9 0
      includes/footer.php
  13. 444 0
      includes/functions.php
  14. 21 0
      includes/header.php
  15. 64 0
      index.php
  16. 185 0
      product.php
  17. 105 0
      reservation.php

+ 110 - 0
admin/index.php

@@ -0,0 +1,110 @@
+<?php
+require_once __DIR__ . '/../config.php';
+require_once __DIR__ . '/../includes/functions.php';
+
+// Check admin login
+if (!isset($_SESSION['admin_logged_in']) || !$_SESSION['admin_logged_in']) {
+    header('Location: login.php');
+    exit;
+}
+
+$pageTitle = 'Admin Dashboard';
+
+// Get statistics
+$products = getProducts();
+$reservations = getReservations();
+
+// Expire old reservations
+expireOldReservations();
+$reservations = getReservations(); // Refresh after expiry
+
+$totalProducts = count($products);
+$totalStock = 0;
+foreach ($products as $product) {
+    $totalStock += getTotalStock($product);
+}
+$openReservations = count(array_filter($reservations, function($r) {
+    return $r['status'] === 'open' && !$r['picked_up'];
+}));
+$pickedUpReservations = count(array_filter($reservations, function($r) {
+    return $r['picked_up'];
+}));
+
+include __DIR__ . '/../includes/header.php';
+?>
+
+<div class="admin-header">
+    <h2>Admin Dashboard</h2>
+    <div>
+        <a href="products.php" class="btn">Produkte verwalten</a>
+        <a href="reservations.php" class="btn">Reservierungen</a>
+        <a href="login.php?logout=1" class="btn btn-secondary">Abmelden</a>
+    </div>
+</div>
+
+<div class="admin-stats">
+    <div class="stat-card">
+        <h3>Produkte</h3>
+        <div class="stat-value"><?php echo $totalProducts; ?></div>
+    </div>
+    
+    <div class="stat-card">
+        <h3>Gesamtbestand</h3>
+        <div class="stat-value"><?php echo $totalStock; ?></div>
+    </div>
+    
+    <div class="stat-card">
+        <h3>Offene Reservierungen</h3>
+        <div class="stat-value"><?php echo $openReservations; ?></div>
+    </div>
+    
+    <div class="stat-card">
+        <h3>Abgeholt</h3>
+        <div class="stat-value"><?php echo $pickedUpReservations; ?></div>
+    </div>
+</div>
+
+<h3 style="margin-top: 2rem;">Letzte Reservierungen</h3>
+<?php
+$recentReservations = array_slice(array_reverse($reservations), 0, 5);
+if (empty($recentReservations)):
+?>
+    <p>Keine Reservierungen vorhanden.</p>
+<?php else: ?>
+    <table>
+        <thead>
+            <tr>
+                <th>Code</th>
+                <th>Kunde</th>
+                <th>Erstellt</th>
+                <th>Status</th>
+                <th>Aktionen</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php foreach ($recentReservations as $reservation): ?>
+                <tr>
+                    <td><strong><?php echo htmlspecialchars($reservation['code']); ?></strong></td>
+                    <td><?php echo htmlspecialchars($reservation['customer_name']); ?></td>
+                    <td><?php echo formatDate($reservation['created']); ?></td>
+                    <td>
+                        <?php
+                        if ($reservation['picked_up']) {
+                            echo '<span style="color: #28a745;">Abgeholt</span>';
+                        } elseif ($reservation['status'] === 'expired') {
+                            echo '<span style="color: #dc3545;">Abgelaufen</span>';
+                        } else {
+                            echo '<span style="color: #ffc107;">Offen</span>';
+                        }
+                        ?>
+                    </td>
+                    <td>
+                        <a href="reservations.php?code=<?php echo urlencode($reservation['code']); ?>" class="btn btn-small">Details</a>
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+<?php endif; ?>
+
+<?php include __DIR__ . '/../includes/footer.php'; ?>

+ 70 - 0
admin/login.php

@@ -0,0 +1,70 @@
+<?php
+require_once __DIR__ . '/../config.php';
+
+// Handle logout
+if (isset($_GET['logout'])) {
+    $_SESSION['admin_logged_in'] = false;
+    session_destroy();
+    header('Location: login.php');
+    exit;
+}
+
+$error = '';
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $password = $_POST['password'] ?? '';
+    
+    if (password_verify($password, ADMIN_PASSWORD_HASH)) {
+        $_SESSION['admin_logged_in'] = true;
+        header('Location: index.php');
+        exit;
+    } else {
+        $error = 'Falsches Passwort.';
+    }
+}
+
+// Redirect if already logged in
+if (isset($_SESSION['admin_logged_in']) && $_SESSION['admin_logged_in']) {
+    header('Location: index.php');
+    exit;
+}
+?>
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Admin Login - <?php echo SITE_NAME; ?></title>
+    <link rel="stylesheet" href="<?php echo SITE_URL; ?>/assets/css/style.css">
+</head>
+<body>
+    <header>
+        <div class="container">
+            <h1><?php echo SITE_NAME; ?> - Admin</h1>
+        </div>
+    </header>
+    <main>
+        <div class="container" style="max-width: 400px; margin-top: 4rem;">
+            <h2>Admin Login</h2>
+            
+            <?php if ($error): ?>
+                <div class="alert alert-error">
+                    <?php echo htmlspecialchars($error); ?>
+                </div>
+            <?php endif; ?>
+            
+            <form method="POST" style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
+                <div class="form-group">
+                    <label for="password">Passwort:</label>
+                    <input type="password" id="password" name="password" required autofocus>
+                </div>
+                <button type="submit" class="btn" style="width: 100%;">Anmelden</button>
+            </form>
+            
+            <div style="text-align: center; margin-top: 1rem;">
+                <a href="<?php echo SITE_URL; ?>/index.php" style="color: #6c757d;">Zurück zum Shop</a>
+            </div>
+        </div>
+    </main>
+</body>
+</html>

+ 350 - 0
admin/products.php

@@ -0,0 +1,350 @@
+<?php
+require_once __DIR__ . '/../config.php';
+require_once __DIR__ . '/../includes/functions.php';
+
+// Check admin login
+if (!isset($_SESSION['admin_logged_in']) || !$_SESSION['admin_logged_in']) {
+    header('Location: login.php');
+    exit;
+}
+
+$pageTitle = 'Produkte verwalten';
+$message = '';
+$messageType = '';
+
+// Handle product operations
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $products = getProducts();
+    
+    if (isset($_POST['add_product'])) {
+        // Generate new ID
+        $newId = 1;
+        if (!empty($products)) {
+            $ids = array_column($products, 'id');
+            $newId = max($ids) + 1;
+        }
+        
+        $newProduct = [
+            'id' => $newId,
+            'name' => sanitize($_POST['name']),
+            'description' => sanitize($_POST['description']),
+            'price' => (float)$_POST['price'],
+            'category' => sanitize($_POST['category']),
+            'image' => sanitize($_POST['image'])
+        ];
+        
+        // Handle stock - per size for apparel, general for merch
+        if ($newProduct['category'] === 'apparel' && !empty($_POST['sizes'])) {
+            $sizes = sanitize($_POST['sizes']);
+            $newProduct['sizes'] = $sizes; // Store as comma-separated string
+            
+            // Initialize stock_by_size
+            $sizeArray = array_map('trim', explode(',', $sizes));
+            $newProduct['stock_by_size'] = [];
+            foreach ($sizeArray as $size) {
+                $stockKey = 'stock_' . str_replace([' ', ','], '_', $size);
+                $newProduct['stock_by_size'][$size] = isset($_POST[$stockKey]) ? (int)$_POST[$stockKey] : 0;
+            }
+        } else {
+            $newProduct['stock'] = (int)$_POST['stock'];
+        }
+        
+        $products[] = $newProduct;
+        saveProducts($products);
+        $message = 'Produkt erfolgreich hinzugefügt.';
+        $messageType = 'success';
+    }
+    
+    if (isset($_POST['update_product'])) {
+        $productId = (int)$_POST['product_id'];
+        foreach ($products as &$product) {
+            if ($product['id'] == $productId) {
+                $product['name'] = sanitize($_POST['name']);
+                $product['description'] = sanitize($_POST['description']);
+                $product['price'] = (float)$_POST['price'];
+                $product['category'] = sanitize($_POST['category']);
+                $product['image'] = sanitize($_POST['image']);
+                
+                // Update stock - per size for apparel, general for merch
+                if ($product['category'] === 'apparel') {
+                    if (!empty($_POST['sizes'])) {
+                        $product['sizes'] = sanitize($_POST['sizes']);
+                        
+                        // Update stock_by_size
+                        $sizeArray = array_map('trim', explode(',', $product['sizes']));
+                        if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
+                            $product['stock_by_size'] = [];
+                        }
+                        foreach ($sizeArray as $size) {
+                            $stockKey = 'stock_' . str_replace([' ', ','], '_', $size);
+                            $product['stock_by_size'][$size] = isset($_POST[$stockKey]) ? (int)$_POST[$stockKey] : (isset($product['stock_by_size'][$size]) ? $product['stock_by_size'][$size] : 0);
+                        }
+                        // Remove sizes that are no longer in the list
+                        $product['stock_by_size'] = array_intersect_key($product['stock_by_size'], array_flip($sizeArray));
+                        unset($product['stock']); // Remove general stock for apparel
+                    } else {
+                        unset($product['sizes']);
+                        unset($product['stock_by_size']);
+                    }
+                } else {
+                    $product['stock'] = (int)$_POST['stock'];
+                    unset($product['sizes']);
+                    unset($product['stock_by_size']);
+                }
+                break;
+            }
+        }
+        saveProducts($products);
+        $message = 'Produkt erfolgreich aktualisiert.';
+        $messageType = 'success';
+    }
+    
+    if (isset($_POST['delete_product'])) {
+        $productId = (int)$_POST['product_id'];
+        $products = array_filter($products, function($product) use ($productId) {
+            return $product['id'] != $productId;
+        });
+        $products = array_values($products); // Re-index
+        saveProducts($products);
+        $message = 'Produkt erfolgreich gelöscht.';
+        $messageType = 'success';
+    }
+}
+
+$products = getProducts();
+$editingProduct = null;
+
+if (isset($_GET['edit'])) {
+    $editingProduct = getProductById((int)$_GET['edit']);
+}
+
+include __DIR__ . '/../includes/header.php';
+?>
+
+<div class="admin-header">
+    <h2>Produkte verwalten</h2>
+    <div>
+        <a href="index.php" class="btn btn-secondary">Zurück zum Dashboard</a>
+    </div>
+</div>
+
+<?php if ($message): ?>
+    <div class="alert alert-<?php echo $messageType; ?>">
+        <?php echo htmlspecialchars($message); ?>
+    </div>
+<?php endif; ?>
+
+<?php if ($editingProduct): ?>
+    <div style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 2rem;">
+        <h3>Produkt bearbeiten</h3>
+        <form method="POST">
+            <input type="hidden" name="product_id" value="<?php echo $editingProduct['id']; ?>">
+            <div class="form-group">
+                <label for="name">Name *</label>
+                <input type="text" id="name" name="name" required value="<?php echo htmlspecialchars($editingProduct['name']); ?>">
+            </div>
+            <div class="form-group">
+                <label for="description">Beschreibung</label>
+                <textarea id="description" name="description" rows="4"><?php echo htmlspecialchars($editingProduct['description']); ?></textarea>
+            </div>
+            <div class="form-group">
+                <label for="price">Preis (€) *</label>
+                <input type="number" id="price" name="price" step="0.01" min="0" required value="<?php echo $editingProduct['price']; ?>">
+            </div>
+            <div class="form-group">
+                <label for="category">Kategorie *</label>
+                <select id="category" name="category" required onchange="toggleStockFields()">
+                    <option value="apparel" <?php echo $editingProduct['category'] === 'apparel' ? 'selected' : ''; ?>>Bekleidung</option>
+                    <option value="merch" <?php echo $editingProduct['category'] === 'merch' ? 'selected' : ''; ?>>Merchandise</option>
+                </select>
+            </div>
+            <div class="form-group" id="stock-group" style="<?php echo $editingProduct['category'] === 'merch' ? '' : 'display: none;'; ?>">
+                <label for="stock">Lagerbestand *</label>
+                <input type="number" id="stock" name="stock" min="0" value="<?php echo isset($editingProduct['stock']) ? (int)$editingProduct['stock'] : 0; ?>">
+            </div>
+            <div class="form-group" id="sizes-group" style="<?php echo $editingProduct['category'] === 'apparel' ? '' : 'display: none;'; ?>">
+                <label for="sizes">Größen (kommagetrennt, z.B. S,M,L,XL,XXL)</label>
+                <input type="text" id="sizes" name="sizes" value="<?php echo isset($editingProduct['sizes']) ? htmlspecialchars($editingProduct['sizes']) : ''; ?>" placeholder="S,M,L,XL" onchange="updateSizeStockFields()">
+            </div>
+            <div id="size-stock-group" style="<?php echo $editingProduct['category'] === 'apparel' ? '' : 'display: none;'; ?>">
+                <?php if ($editingProduct['category'] === 'apparel' && !empty($editingProduct['sizes'])): ?>
+                    <?php 
+                    $sizes = array_map('trim', explode(',', $editingProduct['sizes']));
+                    $stockBySize = isset($editingProduct['stock_by_size']) && is_array($editingProduct['stock_by_size']) ? $editingProduct['stock_by_size'] : [];
+                    foreach ($sizes as $size): 
+                    ?>
+                        <div class="form-group">
+                            <label>Lagerbestand für Größe "<?php echo htmlspecialchars($size); ?>" *</label>
+                            <input type="number" name="stock_<?php echo htmlspecialchars(str_replace([' ', ','], '_', $size)); ?>" min="0" value="<?php echo isset($stockBySize[$size]) ? (int)$stockBySize[$size] : 0; ?>" required>
+                        </div>
+                    <?php endforeach; ?>
+                <?php endif; ?>
+            </div>
+            <div class="form-group">
+                <label for="image">Bilddateiname (z.B. tshirt.jpg)</label>
+                <input type="text" id="image" name="image" value="<?php echo htmlspecialchars($editingProduct['image']); ?>">
+            </div>
+            <script>
+                function toggleStockFields() {
+                    const category = document.getElementById('category').value;
+                    const stockGroup = document.getElementById('stock-group');
+                    const sizesGroup = document.getElementById('sizes-group');
+                    const sizeStockGroup = document.getElementById('size-stock-group');
+                    
+                    if (category === 'apparel') {
+                        stockGroup.style.display = 'none';
+                        sizesGroup.style.display = 'block';
+                        sizeStockGroup.style.display = 'block';
+                        updateSizeStockFields();
+                    } else {
+                        stockGroup.style.display = 'block';
+                        sizesGroup.style.display = 'none';
+                        sizeStockGroup.style.display = 'none';
+                    }
+                }
+                
+                function updateSizeStockFields() {
+                    const sizesInput = document.getElementById('sizes');
+                    const sizeStockGroup = document.getElementById('size-stock-group');
+                    const sizes = sizesInput.value.split(',').map(s => s.trim()).filter(s => s);
+                    
+                    let html = '';
+                    sizes.forEach(size => {
+                        const safeName = size.replace(/[ ,]/g, '_');
+                        html += '<div class="form-group">';
+                        html += '<label>Lagerbestand für Größe "' + size + '" *</label>';
+                        html += '<input type="number" name="stock_' + safeName + '" min="0" value="0" required>';
+                        html += '</div>';
+                    });
+                    sizeStockGroup.innerHTML = html;
+                }
+            </script>
+            <button type="submit" name="update_product" class="btn">Produkt aktualisieren</button>
+            <a href="products.php" class="btn btn-secondary">Abbrechen</a>
+        </form>
+    </div>
+<?php else: ?>
+    <div style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 2rem;">
+        <h3>Neues Produkt hinzufügen</h3>
+        <form method="POST">
+            <div class="form-group">
+                <label for="name">Name *</label>
+                <input type="text" id="name" name="name" required>
+            </div>
+            <div class="form-group">
+                <label for="description">Beschreibung</label>
+                <textarea id="description" name="description" rows="4"></textarea>
+            </div>
+            <div class="form-group">
+                <label for="price">Preis (€) *</label>
+                <input type="number" id="price" name="price" step="0.01" min="0" required>
+            </div>
+            <div class="form-group">
+                <label for="category">Kategorie *</label>
+                <select id="category" name="category" required onchange="toggleStockFields()">
+                    <option value="apparel">Bekleidung</option>
+                    <option value="merch">Merchandise</option>
+                </select>
+            </div>
+            <div class="form-group" id="stock-group" style="display: none;">
+                <label for="stock">Lagerbestand *</label>
+                <input type="number" id="stock" name="stock" min="0" value="0">
+            </div>
+            <div class="form-group" id="sizes-group" style="display: none;">
+                <label for="sizes">Größen (kommagetrennt, z.B. S,M,L,XL,XXL)</label>
+                <input type="text" id="sizes" name="sizes" placeholder="S,M,L,XL" onchange="updateSizeStockFields()">
+            </div>
+            <div id="size-stock-group" style="display: none;"></div>
+            <div class="form-group">
+                <label for="image">Bilddateiname (z.B. tshirt.jpg)</label>
+                <input type="text" id="image" name="image">
+            </div>
+            <script>
+                function toggleStockFields() {
+                    const category = document.getElementById('category').value;
+                    const stockGroup = document.getElementById('stock-group');
+                    const sizesGroup = document.getElementById('sizes-group');
+                    const sizeStockGroup = document.getElementById('size-stock-group');
+                    
+                    if (category === 'apparel') {
+                        stockGroup.style.display = 'none';
+                        sizesGroup.style.display = 'block';
+                        sizeStockGroup.style.display = 'block';
+                        updateSizeStockFields();
+                    } else {
+                        stockGroup.style.display = 'block';
+                        sizesGroup.style.display = 'none';
+                        sizeStockGroup.style.display = 'none';
+                    }
+                }
+                
+                function updateSizeStockFields() {
+                    const sizesInput = document.getElementById('sizes');
+                    const sizeStockGroup = document.getElementById('size-stock-group');
+                    const sizes = sizesInput.value.split(',').map(s => s.trim()).filter(s => s);
+                    
+                    let html = '';
+                    sizes.forEach(size => {
+                        const safeName = size.replace(/[ ,]/g, '_');
+                        html += '<div class="form-group">';
+                        html += '<label>Lagerbestand für Größe "' + size + '" *</label>';
+                        html += '<input type="number" name="stock_' + safeName + '" min="0" value="0" required>';
+                        html += '</div>';
+                    });
+                    sizeStockGroup.innerHTML = html;
+                }
+            </script>
+            <button type="submit" name="add_product" class="btn">Produkt hinzufügen</button>
+        </form>
+    </div>
+<?php endif; ?>
+
+<h3>Alle Produkte</h3>
+<?php if (empty($products)): ?>
+    <p>Keine Produkte vorhanden.</p>
+<?php else: ?>
+    <table>
+        <thead>
+            <tr>
+                <th>ID</th>
+                <th>Name</th>
+                <th>Kategorie</th>
+                <th>Preis</th>
+                <th>Lagerbestand</th>
+                <th>Aktionen</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php foreach ($products as $product): ?>
+                <tr>
+                <td><?php echo $product['id']; ?></td>
+                <td><?php echo htmlspecialchars($product['name']); ?></td>
+                <td><?php echo $product['category'] === 'apparel' ? 'Bekleidung' : 'Merchandise'; ?></td>
+                <td><?php echo formatPrice($product['price']); ?></td>
+                <td>
+                    <?php 
+                    if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
+                        $stockInfo = [];
+                        foreach ($product['stock_by_size'] as $size => $stock) {
+                            $stockInfo[] = "$size: $stock";
+                        }
+                        echo implode(', ', $stockInfo) ?: '0';
+                    } else {
+                        echo isset($product['stock']) ? (int)$product['stock'] : 0;
+                    }
+                    ?>
+                </td>
+                    <td>
+                        <a href="?edit=<?php echo $product['id']; ?>" class="btn btn-small">Bearbeiten</a>
+                        <form method="POST" style="display: inline;" onsubmit="return confirm('Möchten Sie dieses Produkt wirklich löschen?');">
+                            <input type="hidden" name="product_id" value="<?php echo $product['id']; ?>">
+                            <button type="submit" name="delete_product" class="btn btn-secondary btn-small">Löschen</button>
+                        </form>
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+<?php endif; ?>
+
+<?php include __DIR__ . '/../includes/footer.php'; ?>

+ 203 - 0
admin/reservations.php

@@ -0,0 +1,203 @@
+<?php
+require_once __DIR__ . '/../config.php';
+require_once __DIR__ . '/../includes/functions.php';
+
+// Check admin login
+if (!isset($_SESSION['admin_logged_in']) || !$_SESSION['admin_logged_in']) {
+    header('Location: login.php');
+    exit;
+}
+
+$pageTitle = 'Reservierungen verwalten';
+
+// Expire old reservations
+expireOldReservations();
+
+// Handle mark as picked up
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['mark_picked_up'])) {
+    $reservationId = sanitize($_POST['reservation_id']);
+    markReservationPickedUp($reservationId);
+    $message = 'Reservierung als abgeholt markiert.';
+    $messageType = 'success';
+}
+
+$reservations = getReservations();
+$filter = isset($_GET['filter']) ? sanitize($_GET['filter']) : 'all';
+$searchCode = isset($_GET['code']) ? sanitize($_GET['code']) : '';
+
+// Filter reservations
+if ($searchCode) {
+    $reservations = array_filter($reservations, function($r) use ($searchCode) {
+        return stripos($r['code'], $searchCode) !== false || stripos($r['id'], $searchCode) !== false;
+    });
+} else {
+    switch ($filter) {
+        case 'open':
+            $reservations = array_filter($reservations, function($r) {
+                return $r['status'] === 'open' && !$r['picked_up'];
+            });
+            break;
+        case 'picked_up':
+            $reservations = array_filter($reservations, function($r) {
+                return $r['picked_up'];
+            });
+            break;
+        case 'expired':
+            $reservations = array_filter($reservations, function($r) {
+                return $r['status'] === 'expired';
+            });
+            break;
+    }
+}
+
+$reservations = array_reverse($reservations); // Newest first
+
+include __DIR__ . '/../includes/header.php';
+?>
+
+<div class="admin-header">
+    <h2>Reservierungen verwalten</h2>
+    <div>
+        <a href="index.php" class="btn btn-secondary">Zurück zum Dashboard</a>
+    </div>
+</div>
+
+<?php if (isset($message)): ?>
+    <div class="alert alert-<?php echo $messageType; ?>">
+        <?php echo htmlspecialchars($message); ?>
+    </div>
+<?php endif; ?>
+
+<div style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 2rem;">
+    <form method="GET" style="display: flex; gap: 1rem; align-items: end; flex-wrap: wrap;">
+        <div style="flex: 1; min-width: 200px;">
+            <label for="code">Code/ID suchen:</label>
+            <input type="text" id="code" name="code" value="<?php echo htmlspecialchars($searchCode); ?>" placeholder="Abholcode oder Reservierungsnummer">
+        </div>
+        <div>
+            <label for="filter">Filter:</label>
+            <select id="filter" name="filter">
+                <option value="all" <?php echo $filter === 'all' ? 'selected' : ''; ?>>Alle</option>
+                <option value="open" <?php echo $filter === 'open' ? 'selected' : ''; ?>>Offen</option>
+                <option value="picked_up" <?php echo $filter === 'picked_up' ? 'selected' : ''; ?>>Abgeholt</option>
+                <option value="expired" <?php echo $filter === 'expired' ? 'selected' : ''; ?>>Abgelaufen</option>
+            </select>
+        </div>
+        <div>
+            <button type="submit" class="btn">Filtern</button>
+            <a href="reservations.php" class="btn btn-secondary">Zurücksetzen</a>
+        </div>
+    </form>
+</div>
+
+<?php if (empty($reservations)): ?>
+    <div class="alert alert-info">
+        <p>Keine Reservierungen gefunden.</p>
+    </div>
+<?php else: ?>
+    <table>
+        <thead>
+            <tr>
+                <th>Code</th>
+                <th>Kunde</th>
+                <th>E-Mail</th>
+                <th>Artikel</th>
+                <th>Erstellt</th>
+                <th>Gültig bis</th>
+                <th>Status</th>
+                <th>Aktionen</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php foreach ($reservations as $reservation): ?>
+                <tr>
+                    <td><strong><?php echo htmlspecialchars($reservation['code']); ?></strong></td>
+                    <td><?php echo htmlspecialchars($reservation['customer_name']); ?></td>
+                    <td><?php echo htmlspecialchars($reservation['customer_email']); ?></td>
+                    <td>
+                        <?php
+                        $itemCount = 0;
+                        foreach ($reservation['items'] as $item) {
+                            $itemCount += $item['quantity'];
+                        }
+                        echo $itemCount . ' Artikel';
+                        ?>
+                    </td>
+                    <td><?php echo formatDate($reservation['created']); ?></td>
+                    <td><?php echo formatDate($reservation['expires']); ?></td>
+                    <td>
+                        <?php
+                        if ($reservation['picked_up']) {
+                            echo '<span style="color: #28a745;">Abgeholt</span>';
+                        } elseif ($reservation['status'] === 'expired') {
+                            echo '<span style="color: #dc3545;">Abgelaufen</span>';
+                        } else {
+                            echo '<span style="color: #ffc107;">Offen</span>';
+                        }
+                        ?>
+                    </td>
+                    <td>
+                        <?php if (!$reservation['picked_up'] && $reservation['status'] === 'open'): ?>
+                            <form method="POST" style="display: inline;" onsubmit="return confirm('Reservierung als abgeholt markieren?');">
+                                <input type="hidden" name="reservation_id" value="<?php echo htmlspecialchars($reservation['id']); ?>">
+                                <button type="submit" name="mark_picked_up" class="btn btn-small">Als abgeholt markieren</button>
+                            </form>
+                        <?php endif; ?>
+                        <button onclick="showDetails('<?php echo htmlspecialchars($reservation['id']); ?>')" class="btn btn-secondary btn-small">Details</button>
+                    </td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+<?php endif; ?>
+
+<!-- Details Modal -->
+<div id="detailsModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; align-items: center; justify-content: center;">
+    <div style="background: white; padding: 2rem; border-radius: 8px; max-width: 600px; max-height: 80vh; overflow-y: auto; position: relative;">
+        <button onclick="closeDetails()" style="position: absolute; top: 1rem; right: 1rem; background: #dc3545; color: white; border: none; border-radius: 4px; padding: 0.5rem 1rem; cursor: pointer;">Schließen</button>
+        <div id="detailsContent"></div>
+    </div>
+</div>
+
+<script>
+function showDetails(reservationId) {
+    const reservations = <?php echo json_encode(getReservations()); ?>;
+    const reservation = reservations.find(r => r.id === reservationId);
+    
+    if (!reservation) return;
+    
+    let itemsHtml = '<h3>Artikel:</h3><ul>';
+    reservation.items.forEach(item => {
+        const product = <?php echo json_encode(getProducts()); ?>.find(p => p.id == item.product_id);
+        if (product) {
+            let sizeInfo = '';
+            if (item.size && item.size !== '') {
+                sizeInfo = ` - Größe: ${item.size}`;
+            }
+            itemsHtml += `<li>${product.name}${sizeInfo} - Menge: ${item.quantity}</li>`;
+        }
+    });
+    itemsHtml += '</ul>';
+    
+    const html = `
+        <h2>Reservierungsdetails</h2>
+        <p><strong>Reservierungsnummer:</strong> ${reservation.id}</p>
+        <p><strong>Abholcode:</strong> <strong style="font-size: 1.5rem; color: #c41e3a;">${reservation.code}</strong></p>
+        <p><strong>Kunde:</strong> ${reservation.customer_name}</p>
+        <p><strong>E-Mail:</strong> ${reservation.customer_email}</p>
+        <p><strong>Erstellt:</strong> ${reservation.created}</p>
+        <p><strong>Gültig bis:</strong> ${reservation.expires}</p>
+        <p><strong>Status:</strong> ${reservation.picked_up ? 'Abgeholt' : (reservation.status === 'expired' ? 'Abgelaufen' : 'Offen')}</p>
+        ${itemsHtml}
+    `;
+    
+    document.getElementById('detailsContent').innerHTML = html;
+    document.getElementById('detailsModal').style.display = 'flex';
+}
+
+function closeDetails() {
+    document.getElementById('detailsModal').style.display = 'none';
+}
+</script>
+
+<?php include __DIR__ . '/../includes/footer.php'; ?>

+ 370 - 0
assets/css/style.css

@@ -0,0 +1,370 @@
+/* Reset and Base Styles */
+* {
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+
+body {
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+    line-height: 1.6;
+    color: #333;
+    background-color: #f5f5f5;
+}
+
+.container {
+    max-width: 1200px;
+    margin: 0 auto;
+    padding: 0 20px;
+}
+
+/* Header */
+header {
+    background-color: #c41e3a;
+    color: white;
+    padding: 1rem 0;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+header h1 {
+    font-size: 1.8rem;
+    margin-bottom: 0.5rem;
+}
+
+header nav {
+    margin-top: 0.5rem;
+}
+
+header nav a {
+    color: white;
+    text-decoration: none;
+    margin-right: 1.5rem;
+    font-weight: 500;
+}
+
+header nav a:hover {
+    text-decoration: underline;
+}
+
+/* Main Content */
+main {
+    min-height: calc(100vh - 200px);
+    padding: 2rem 0;
+}
+
+/* Buttons */
+.btn {
+    display: inline-block;
+    padding: 0.75rem 1.5rem;
+    background-color: #c41e3a;
+    color: white;
+    text-decoration: none;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+    font-size: 1rem;
+    font-weight: 500;
+    transition: background-color 0.3s;
+}
+
+.btn:hover {
+    background-color: #a01a2e;
+}
+
+.btn-secondary {
+    background-color: #6c757d;
+}
+
+.btn-secondary:hover {
+    background-color: #5a6268;
+}
+
+.btn-small {
+    padding: 0.5rem 1rem;
+    font-size: 0.9rem;
+}
+
+/* Product Grid */
+.products-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+    gap: 2rem;
+    margin-top: 2rem;
+}
+
+.product-card {
+    background: white;
+    border-radius: 8px;
+    overflow: hidden;
+    box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+    transition: transform 0.3s, box-shadow 0.3s;
+}
+
+.product-card:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+
+.product-card img {
+    width: 100%;
+    height: 200px;
+    object-fit: cover;
+    background-color: #e9ecef;
+}
+
+.product-card-content {
+    padding: 1.5rem;
+}
+
+.product-card h3 {
+    font-size: 1.2rem;
+    margin-bottom: 0.5rem;
+    color: #c41e3a;
+}
+
+.product-card .price {
+    font-size: 1.5rem;
+    font-weight: bold;
+    color: #333;
+    margin: 0.5rem 0;
+}
+
+.product-card .stock {
+    font-size: 0.9rem;
+    color: #6c757d;
+    margin-bottom: 1rem;
+}
+
+.product-card .stock.in-stock {
+    color: #28a745;
+}
+
+.product-card .stock.out-of-stock {
+    color: #dc3545;
+}
+
+/* Forms */
+.form-group {
+    margin-bottom: 1.5rem;
+}
+
+.form-group label {
+    display: block;
+    margin-bottom: 0.5rem;
+    font-weight: 500;
+}
+
+.form-group input,
+.form-group textarea,
+.form-group select {
+    width: 100%;
+    padding: 0.75rem;
+    border: 1px solid #ddd;
+    border-radius: 4px;
+    font-size: 1rem;
+    font-family: inherit;
+}
+
+.form-group input:focus,
+.form-group textarea:focus,
+.form-group select:focus {
+    outline: none;
+    border-color: #c41e3a;
+}
+
+/* Cart */
+.cart-item {
+    background: white;
+    padding: 1.5rem;
+    margin-bottom: 1rem;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+
+.cart-item-info {
+    flex: 1;
+}
+
+.cart-item-actions {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+}
+
+.quantity-input {
+    width: 60px;
+    padding: 0.5rem;
+    text-align: center;
+}
+
+/* Tables */
+table {
+    width: 100%;
+    background: white;
+    border-collapse: collapse;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    border-radius: 8px;
+    overflow: hidden;
+}
+
+table th {
+    background-color: #c41e3a;
+    color: white;
+    padding: 1rem;
+    text-align: left;
+    font-weight: 600;
+}
+
+table td {
+    padding: 1rem;
+    border-top: 1px solid #e9ecef;
+}
+
+table tr:hover {
+    background-color: #f8f9fa;
+}
+
+/* Alerts */
+.alert {
+    padding: 1rem;
+    border-radius: 4px;
+    margin-bottom: 1.5rem;
+}
+
+.alert-success {
+    background-color: #d4edda;
+    color: #155724;
+    border: 1px solid #c3e6cb;
+}
+
+.alert-error {
+    background-color: #f8d7da;
+    color: #721c24;
+    border: 1px solid #f5c6cb;
+}
+
+.alert-info {
+    background-color: #d1ecf1;
+    color: #0c5460;
+    border: 1px solid #bee5eb;
+}
+
+/* Footer */
+footer {
+    background-color: #333;
+    color: white;
+    text-align: center;
+    padding: 2rem 0;
+    margin-top: 3rem;
+}
+
+/* Reservation Code */
+.reservation-code {
+    background: #fff3cd;
+    border: 2px solid #ffc107;
+    padding: 2rem;
+    border-radius: 8px;
+    text-align: center;
+    margin: 2rem 0;
+}
+
+.reservation-code h2 {
+    font-size: 2rem;
+    color: #856404;
+    letter-spacing: 0.2rem;
+    font-family: 'Courier New', monospace;
+}
+
+/* Admin Styles */
+.admin-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 2rem;
+}
+
+.admin-stats {
+    display: grid;
+    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+    gap: 1.5rem;
+    margin-bottom: 2rem;
+}
+
+.stat-card {
+    background: white;
+    padding: 1.5rem;
+    border-radius: 8px;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.stat-card h3 {
+    font-size: 0.9rem;
+    color: #6c757d;
+    margin-bottom: 0.5rem;
+}
+
+.stat-card .stat-value {
+    font-size: 2rem;
+    font-weight: bold;
+    color: #c41e3a;
+}
+
+/* Product Detail Grid */
+.product-detail-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 2rem;
+    margin-top: 2rem;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+    .products-grid {
+        grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+        gap: 1rem;
+    }
+    
+    .product-detail-grid {
+        grid-template-columns: 1fr;
+        gap: 1.5rem;
+    }
+    
+    .cart-item {
+        flex-direction: column;
+        align-items: flex-start;
+    }
+    
+    .cart-item-actions {
+        width: 100%;
+        justify-content: space-between;
+        margin-top: 1rem;
+    }
+    
+    table {
+        font-size: 0.9rem;
+    }
+    
+    table th,
+    table td {
+        padding: 0.5rem;
+    }
+    
+    .admin-stats {
+        grid-template-columns: 1fr;
+    }
+}
+
+/* Utility Classes */
+.text-center {
+    text-align: center;
+}
+
+.mt-1 { margin-top: 0.5rem; }
+.mt-2 { margin-top: 1rem; }
+.mt-3 { margin-top: 1.5rem; }
+.mb-1 { margin-bottom: 0.5rem; }
+.mb-2 { margin-bottom: 1rem; }
+.mb-3 { margin-bottom: 1.5rem; }

+ 129 - 0
cart.php

@@ -0,0 +1,129 @@
+<?php
+require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/includes/functions.php';
+
+$pageTitle = 'Warenkorb';
+
+// Handle cart updates
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    if (isset($_POST['update_cart'])) {
+        $quantities = $_POST['quantities'] ?? [];
+        foreach ($quantities as $productId => $quantity) {
+            $quantity = (int)$quantity;
+            if ($quantity <= 0) {
+                // Remove from cart
+                $_SESSION['cart'] = array_filter($_SESSION['cart'], function($item) use ($productId) {
+                    return $item['product_id'] != $productId;
+                });
+                $_SESSION['cart'] = array_values($_SESSION['cart']); // Re-index
+            } else {
+                // Update quantity
+                foreach ($_SESSION['cart'] as &$item) {
+                    if ($item['product_id'] == $productId) {
+                        $item['quantity'] = $quantity;
+                        break;
+                    }
+                }
+            }
+        }
+    } elseif (isset($_POST['remove_item'])) {
+        $productId = (int)$_POST['product_id'];
+        $size = isset($_POST['size']) ? sanitize($_POST['size']) : null;
+        
+        $_SESSION['cart'] = array_filter($_SESSION['cart'], function($item) use ($productId, $size) {
+            if ($item['product_id'] != $productId) {
+                return true;
+            }
+            // If size is specified, only remove items with matching size
+            if ($size !== null) {
+                return !(isset($item['size']) && $item['size'] === $size);
+            }
+            // If no size specified, remove all items of this product
+            return false;
+        });
+        $_SESSION['cart'] = array_values($_SESSION['cart']); // Re-index
+    }
+}
+
+$cart = $_SESSION['cart'] ?? [];
+$cartItems = [];
+$total = 0;
+
+foreach ($cart as $index => $item) {
+    $product = getProductById($item['product_id']);
+    if ($product) {
+        $itemTotal = $product['price'] * $item['quantity'];
+        $total += $itemTotal;
+        $cartItems[] = [
+            'product' => $product,
+            'quantity' => $item['quantity'],
+            'total' => $itemTotal,
+            'size' => isset($item['size']) ? $item['size'] : null,
+            'cart_index' => $index
+        ];
+    }
+}
+
+include __DIR__ . '/includes/header.php';
+?>
+
+<h2>Warenkorb</h2>
+
+<?php if (empty($cartItems)): ?>
+    <div class="alert alert-info">
+        <p>Ihr Warenkorb ist leer.</p>
+        <a href="index.php" class="btn">Weiter einkaufen</a>
+    </div>
+<?php else: ?>
+    <form method="POST">
+        <?php foreach ($cartItems as $cartItem): ?>
+            <div class="cart-item">
+                <div class="cart-item-info">
+                    <h3><?php echo htmlspecialchars($cartItem['product']['name']); ?></h3>
+                    <?php if (isset($cartItem['size']) && !empty($cartItem['size'])): ?>
+                        <p><strong>Größe:</strong> <?php echo htmlspecialchars($cartItem['size']); ?></p>
+                    <?php endif; ?>
+                    <p>Einzelpreis: <?php echo formatPrice($cartItem['product']['price']); ?></p>
+                    <p>Gesamt: <?php echo formatPrice($cartItem['total']); ?></p>
+                    <?php 
+                    $itemStock = getStock($cartItem['product'], isset($cartItem['size']) ? $cartItem['size'] : null);
+                    $hasEnoughStock = $itemStock >= $cartItem['quantity'];
+                    ?>
+                    <p class="stock <?php echo $hasEnoughStock ? 'in-stock' : 'out-of-stock'; ?>">
+                        Lagerbestand: <?php echo $itemStock; ?> Stück
+                        <?php if (!$hasEnoughStock): ?>
+                            <br><strong style="color: #dc3545;">Nicht genügend Lagerbestand!</strong>
+                        <?php endif; ?>
+                    </p>
+                </div>
+                <div class="cart-item-actions">
+                    <label>
+                        Menge:
+                        <input type="number" name="quantities[<?php echo $cartItem['product']['id']; ?>]" 
+                               value="<?php echo $cartItem['quantity']; ?>" 
+                               min="0" 
+                               max="<?php echo $itemStock; ?>"
+                               class="quantity-input">
+                    </label>
+                    <form method="POST" style="display: inline;">
+                        <input type="hidden" name="product_id" value="<?php echo $cartItem['product']['id']; ?>">
+                        <?php if (isset($cartItem['size']) && !empty($cartItem['size'])): ?>
+                            <input type="hidden" name="size" value="<?php echo htmlspecialchars($cartItem['size']); ?>">
+                        <?php endif; ?>
+                        <button type="submit" name="remove_item" value="1" class="btn btn-secondary btn-small">Entfernen</button>
+                    </form>
+                </div>
+            </div>
+        <?php endforeach; ?>
+        
+        <div style="text-align: right; margin: 2rem 0;">
+            <div style="font-size: 1.5rem; font-weight: bold; margin-bottom: 1rem;">
+                Gesamtsumme: <?php echo formatPrice($total); ?>
+            </div>
+            <button type="submit" name="update_cart" class="btn btn-secondary">Warenkorb aktualisieren</button>
+            <a href="checkout.php" class="btn" style="margin-left: 1rem;">Zur Reservierung</a>
+        </div>
+    </form>
+<?php endif; ?>
+
+<?php include __DIR__ . '/includes/footer.php'; ?>

+ 146 - 0
checkout.php

@@ -0,0 +1,146 @@
+<?php
+require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/includes/functions.php';
+
+$pageTitle = 'Reservierung';
+
+$cart = $_SESSION['cart'] ?? [];
+if (empty($cart)) {
+    header('Location: cart.php');
+    exit;
+}
+
+// Validate cart items and stock
+$cartItems = [];
+$errors = [];
+$total = 0;
+
+foreach ($cart as $item) {
+    $product = getProductById($item['product_id']);
+    if (!$product) {
+        $errors[] = 'Ein Produkt wurde nicht gefunden.';
+        continue;
+    }
+    
+    $size = isset($item['size']) ? $item['size'] : null;
+    if (!checkStock($item['product_id'], $item['quantity'], $size)) {
+        $sizeInfo = $size ? " (Größe: $size)" : '';
+        $errors[] = 'Nicht genügend Lagerbestand für: ' . $product['name'] . $sizeInfo;
+        continue;
+    }
+    
+    $itemTotal = $product['price'] * $item['quantity'];
+    $total += $itemTotal;
+    $cartItems[] = [
+        'product' => $product,
+        'quantity' => $item['quantity'],
+        'total' => $itemTotal,
+        'size' => isset($item['size']) ? $item['size'] : null
+    ];
+}
+
+// Handle form submission
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_reservation'])) {
+    $customerName = sanitize($_POST['customer_name'] ?? '');
+    $customerEmail = sanitize($_POST['customer_email'] ?? '');
+    
+    if (empty($customerName)) {
+        $errors[] = 'Bitte geben Sie Ihren Namen ein.';
+    }
+    
+    if (empty($customerEmail) || !filter_var($customerEmail, FILTER_VALIDATE_EMAIL)) {
+        $errors[] = 'Bitte geben Sie eine gültige E-Mail-Adresse ein.';
+    }
+    
+    if (empty($errors)) {
+        // Create reservation
+        $items = [];
+        foreach ($cart as $cartItem) {
+            $item = [
+                'product_id' => $cartItem['product_id'],
+                'quantity' => $cartItem['quantity']
+            ];
+            if (isset($cartItem['size']) && !empty($cartItem['size'])) {
+                $item['size'] = $cartItem['size'];
+            }
+            $items[] = $item;
+        }
+        
+        $result = createReservation($customerName, $customerEmail, $items);
+        
+        if ($result['success']) {
+            // Clear cart
+            $_SESSION['cart'] = [];
+            // Redirect to reservation confirmation
+            header('Location: reservation.php?code=' . urlencode($result['reservation']['code']));
+            exit;
+        } else {
+            $errors[] = $result['message'];
+        }
+    }
+}
+
+include __DIR__ . '/includes/header.php';
+?>
+
+<h2>Reservierung abschließen</h2>
+
+<?php if (!empty($errors)): ?>
+    <div class="alert alert-error">
+        <ul style="margin-left: 1.5rem;">
+            <?php foreach ($errors as $error): ?>
+                <li><?php echo htmlspecialchars($error); ?></li>
+            <?php endforeach; ?>
+        </ul>
+    </div>
+<?php endif; ?>
+
+<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-top: 2rem;">
+    <div>
+        <h3>Ihre Bestellung</h3>
+        <?php foreach ($cartItems as $cartItem): ?>
+            <div style="background: white; padding: 1rem; margin-bottom: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
+                <strong><?php echo htmlspecialchars($cartItem['product']['name']); ?></strong><br>
+                <?php if (isset($cartItem['size']) && !empty($cartItem['size'])): ?>
+                    Größe: <?php echo htmlspecialchars($cartItem['size']); ?><br>
+                <?php endif; ?>
+                Menge: <?php echo $cartItem['quantity']; ?><br>
+                Preis: <?php echo formatPrice($cartItem['total']); ?>
+            </div>
+        <?php endforeach; ?>
+        
+        <div style="background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-top: 1rem;">
+            <strong style="font-size: 1.2rem;">Gesamtsumme: <?php echo formatPrice($total); ?></strong>
+        </div>
+    </div>
+    
+    <div>
+        <h3>Ihre Daten</h3>
+        <form method="POST">
+            <div class="form-group">
+                <label for="customer_name">Name *</label>
+                <input type="text" id="customer_name" name="customer_name" required 
+                       value="<?php echo isset($_POST['customer_name']) ? htmlspecialchars($_POST['customer_name']) : ''; ?>">
+            </div>
+            
+            <div class="form-group">
+                <label for="customer_email">E-Mail-Adresse *</label>
+                <input type="email" id="customer_email" name="customer_email" required 
+                       value="<?php echo isset($_POST['customer_email']) ? htmlspecialchars($_POST['customer_email']) : ''; ?>">
+            </div>
+            
+            <div class="alert alert-info">
+                <strong>Hinweis:</strong> Nach der Reservierung erhalten Sie einen Abholcode, den Sie bei der Abholung vorzeigen müssen. 
+                Die Reservierung ist <?php echo RESERVATION_EXPIRY_DAYS; ?> Tage gültig.
+            </div>
+            
+            <button type="submit" name="create_reservation" class="btn" style="width: 100%;">Reservierung abschließen</button>
+        </form>
+        
+        <div style="margin-top: 1rem;">
+            <a href="cart.php" class="btn btn-secondary">Zurück zum Warenkorb</a>
+        </div>
+    </div>
+</div>
+
+<?php include __DIR__ . '/includes/footer.php'; ?>

+ 38 - 0
config.php

@@ -0,0 +1,38 @@
+<?php
+// Configuration file for the webshop
+
+// Site settings
+define('SITE_NAME', 'Feuerwehr Freising Test Shop');
+define('SITE_URL', ''); // Leave empty for relative URLs
+
+// Admin settings
+// Default password: admin123
+// Change this hash after first login!
+//
+// To generate a new password hash in bash (using Python bcrypt):
+// python3 -c "import bcrypt; print(bcrypt.hashpw(b'your_new_password', bcrypt.gensalt(rounds=10, prefix=b'2y')).decode())"
+// 
+// Alternative using htpasswd (if Apache tools are installed):
+// htpasswd -bnBC 10 "" your_new_password | sed 's/^://' | sed 's/\$2y\$/\$2y\$/'
+//
+// Copy the output hash and replace the value below.
+//
+define('ADMIN_PASSWORD_HASH', '$2y$10$gArNDW.HhPmDcwYJ/xWRiOPkNop3695UIYzkV.G8WHQRUtLJVPLhy');
+
+// Reservation settings
+define('RESERVATION_EXPIRY_DAYS', 60);
+
+// Email settings
+define('ADMIN_EMAIL', 'inbox@medowar.de'); // Change to your admin email
+define('FROM_EMAIL', 'shop@medowar.de'); // Change to your sender email
+define('FROM_NAME', SITE_NAME);
+
+// Data file paths
+define('DATA_DIR', __DIR__ . '/data/');
+define('PRODUCTS_FILE', DATA_DIR . 'products.json');
+define('RESERVATIONS_FILE', DATA_DIR . 'reservations.json');
+
+// Session settings
+if (session_status() === PHP_SESSION_NONE) {
+    session_start();
+}

+ 3 - 0
data/.htaccess

@@ -0,0 +1,3 @@
+# Protect data directory from direct access
+Order Deny,Allow
+Deny from all

+ 98 - 0
data/products.json

@@ -0,0 +1,98 @@
+{
+    "products": [
+        {
+            "id": 1,
+            "name": "Feuerwehr T-Shirt",
+            "description": "Hochwertiges T-Shirt aus 100% Baumwolle mit Feuerwehr-Logo. Perfekt für den Alltag oder als Geschenk.\n\nFarbe: Rot",
+            "price": 19.99,
+            "category": "apparel",
+            "image": "",
+            "sizes": "S,M,L,XL",
+            "stock_by_size": {
+                "S": 12,
+                "M": 15,
+                "L": 18,
+                "XL": 5
+            }
+        },
+        {
+            "id": 2,
+            "name": "Feuerwehr Polo-Shirt",
+            "description": "Elegantes Polo-Shirt mit gesticktem Feuerwehr-Emblem. Ideal für offizielle Anlässe.\n\nFarbe: Dunkelblau",
+            "price": 29.99,
+            "category": "apparel",
+            "image": "",
+            "sizes": "S,M,L,XL,XXL",
+            "stock_by_size": {
+                "S": 5,
+                "M": 8,
+                "L": 10,
+                "XL": 5,
+                "XXL": 2
+            }
+        },
+        {
+            "id": 3,
+            "name": "Feuerwehr Kapuzenpullover",
+            "description": "Warm und gemütlich - unser Kapuzenpullover mit Feuerwehr-Aufdruck. Perfekt für kältere Tage.\n\nFarbe: Schwarz",
+            "price": 39.99,
+            "category": "apparel",
+            "image": "",
+            "sizes": "M,L,XL,XXL",
+            "stock_by_size": {
+                "M": 5,
+                "L": 8,
+                "XL": 10,
+                "XXL": 2
+            }
+        },
+        {
+            "id": 4,
+            "name": "Feuerwehr Basecap",
+            "description": "Klassische Basecap mit Feuerwehr-Logo. Schützt vor Sonne und sieht dabei noch gut aus.\n\nEinheitsgröße, verstellbar",
+            "price": 14.99,
+            "category": "apparel",
+            "image": "",
+            "sizes": "Einheitsgröße",
+            "stock_by_size": {
+                "Einheitsgröße": 40
+            }
+        },
+        {
+            "id": 5,
+            "name": "Feuerwehr Tasse",
+            "description": "Keramik-Tasse mit Feuerwehr-Motiv. Ideal für Kaffee oder Tee zu Hause oder im Büro.\n\nFassungsvermögen: 330ml",
+            "price": 9.99,
+            "stock": 60,
+            "category": "merch",
+            "image": ""
+        },
+        {
+            "id": 6,
+            "name": "Feuerwehr Aufkleber-Set",
+            "description": "Set mit verschiedenen Feuerwehr-Aufklebern. Perfekt für Auto, Laptop oder andere Oberflächen.\n\nEnthält 10 verschiedene Motive",
+            "price": 4.99,
+            "stock": 100,
+            "category": "merch",
+            "image": ""
+        },
+        {
+            "id": 7,
+            "name": "Feuerwehr Schlüsselanhänger",
+            "description": "Robuster Schlüsselanhänger aus Metall mit Feuerwehr-Logo. Ein praktisches Accessoire für jeden Tag.",
+            "price": 7.99,
+            "stock": 80,
+            "category": "merch",
+            "image": ""
+        },
+        {
+            "id": 8,
+            "name": "Feuerwehr Kugelschreiber",
+            "description": "Hochwertiger Kugelschreiber mit Feuerwehr-Logo. Ideal für Notizen und Unterschriften.\n\nFarbe: Rot",
+            "price": 3.99,
+            "stock": 120,
+            "category": "merch",
+            "image": ""
+        }
+    ]
+}

+ 3 - 0
data/reservations.json

@@ -0,0 +1,3 @@
+{
+    "reservations": []
+}

+ 9 - 0
includes/footer.php

@@ -0,0 +1,9 @@
+        </div>
+    </main>
+    <footer>
+        <div class="container">
+            <p>&copy; <?php echo date('Y'); ?> <?php echo SITE_NAME; ?>. Alle Rechte vorbehalten.</p>
+        </div>
+    </footer>
+</body>
+</html>

+ 444 - 0
includes/functions.php

@@ -0,0 +1,444 @@
+<?php
+require_once __DIR__ . '/../config.php';
+
+/**
+ * Read JSON file and return decoded data
+ */
+function readJsonFile($file) {
+    if (!file_exists($file)) {
+        return [];
+    }
+    $content = file_get_contents($file);
+    if (empty($content)) {
+        return [];
+    }
+    $data = json_decode($content, true);
+    return $data ? $data : [];
+}
+
+/**
+ * Write data to JSON file
+ */
+function writeJsonFile($file, $data) {
+    $dir = dirname($file);
+    if (!is_dir($dir)) {
+        mkdir($dir, 0755, true);
+    }
+    file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
+}
+
+/**
+ * Get all products
+ */
+function getProducts() {
+    $data = readJsonFile(PRODUCTS_FILE);
+    return isset($data['products']) ? $data['products'] : [];
+}
+
+/**
+ * Get product by ID
+ */
+function getProductById($id) {
+    $products = getProducts();
+    foreach ($products as $product) {
+        if ($product['id'] == $id) {
+            return $product;
+        }
+    }
+    return null;
+}
+
+/**
+ * Save products
+ */
+function saveProducts($products) {
+    $data = ['products' => $products];
+    writeJsonFile(PRODUCTS_FILE, $data);
+}
+
+/**
+ * Get all reservations
+ */
+function getReservations() {
+    $data = readJsonFile(RESERVATIONS_FILE);
+    return isset($data['reservations']) ? $data['reservations'] : [];
+}
+
+/**
+ * Get reservation by code
+ */
+function getReservationByCode($code) {
+    $reservations = getReservations();
+    foreach ($reservations as $reservation) {
+        if ($reservation['code'] === $code) {
+            return $reservation;
+        }
+    }
+    return null;
+}
+
+/**
+ * Save reservations
+ */
+function saveReservations($reservations) {
+    $data = ['reservations' => $reservations];
+    writeJsonFile(RESERVATIONS_FILE, $data);
+}
+
+/**
+ * Generate unique reservation code
+ */
+function generateReservationCode() {
+    $code = '';
+    $characters = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing characters
+    $max = strlen($characters) - 1;
+    
+    do {
+        $code = '';
+        for ($i = 0; $i < 6; $i++) {
+            $code .= $characters[random_int(0, $max)];
+        }
+        // Check if code already exists
+        $existing = getReservationByCode($code);
+    } while ($existing !== null);
+    
+    return $code;
+}
+
+/**
+ * Generate reservation ID
+ */
+function generateReservationId() {
+    $reservations = getReservations();
+    $count = count($reservations) + 1;
+    $year = date('Y');
+    return sprintf('RES-%s-%03d', $year, $count);
+}
+
+/**
+ * Check if product has enough stock
+ * For apparel: checks stock for specific size
+ * For merch: checks general stock
+ */
+function checkStock($productId, $quantity, $size = null) {
+    $product = getProductById($productId);
+    if (!$product) {
+        return false;
+    }
+    
+    // For apparel with sizes, check stock per size
+    if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
+        if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
+            return false;
+        }
+        $sizeStock = isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
+        return $sizeStock >= $quantity;
+    }
+    
+    // For merch or apparel without size-specific stock, use general stock
+    $stock = isset($product['stock']) ? (int)$product['stock'] : 0;
+    return $stock >= $quantity;
+}
+
+/**
+ * Get stock for a product (per size for apparel, general for merch)
+ */
+function getStock($product, $size = null) {
+    if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
+        if (isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
+            return isset($product['stock_by_size'][$size]) ? (int)$product['stock_by_size'][$size] : 0;
+        }
+    }
+    return isset($product['stock']) ? (int)$product['stock'] : 0;
+}
+
+/**
+ * Get total stock for a product (sum of all sizes for apparel)
+ */
+function getTotalStock($product) {
+    if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])) {
+        return array_sum($product['stock_by_size']);
+    }
+    return isset($product['stock']) ? (int)$product['stock'] : 0;
+}
+
+/**
+ * Allocate stock for reservation
+ */
+function allocateStock($productId, $quantity, $size = null) {
+    $products = getProducts();
+    foreach ($products as &$product) {
+        if ($product['id'] == $productId) {
+            // For apparel with sizes, allocate per size
+            if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
+                if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
+                    $product['stock_by_size'] = [];
+                }
+                if (!isset($product['stock_by_size'][$size])) {
+                    $product['stock_by_size'][$size] = 0;
+                }
+                $product['stock_by_size'][$size] -= $quantity;
+                if ($product['stock_by_size'][$size] < 0) {
+                    $product['stock_by_size'][$size] = 0;
+                }
+            } else {
+                // For merch or general stock
+                if (!isset($product['stock'])) {
+                    $product['stock'] = 0;
+                }
+                $product['stock'] -= $quantity;
+                if ($product['stock'] < 0) {
+                    $product['stock'] = 0;
+                }
+            }
+            break;
+        }
+    }
+    saveProducts($products);
+}
+
+/**
+ * Release stock from reservation
+ */
+function releaseStock($productId, $quantity, $size = null) {
+    $products = getProducts();
+    foreach ($products as &$product) {
+        if ($product['id'] == $productId) {
+            // For apparel with sizes, release per size
+            if ($product['category'] === 'apparel' && !empty($product['sizes']) && $size !== null) {
+                if (!isset($product['stock_by_size']) || !is_array($product['stock_by_size'])) {
+                    $product['stock_by_size'] = [];
+                }
+                if (!isset($product['stock_by_size'][$size])) {
+                    $product['stock_by_size'][$size] = 0;
+                }
+                $product['stock_by_size'][$size] += $quantity;
+            } else {
+                // For merch or general stock
+                if (!isset($product['stock'])) {
+                    $product['stock'] = 0;
+                }
+                $product['stock'] += $quantity;
+            }
+            break;
+        }
+    }
+    saveProducts($products);
+}
+
+/**
+ * Create new reservation
+ */
+function createReservation($customerName, $customerEmail, $items) {
+    $reservations = getReservations();
+    
+    // Validate stock for all items
+    foreach ($items as $item) {
+        $size = isset($item['size']) ? $item['size'] : null;
+        if (!checkStock($item['product_id'], $item['quantity'], $size)) {
+            $product = getProductById($item['product_id']);
+            $productName = $product ? $product['name'] : 'Produkt';
+            $sizeInfo = $size ? " (Größe: $size)" : '';
+            return ['success' => false, 'message' => "Nicht genügend Lagerbestand für: $productName$sizeInfo"];
+        }
+    }
+    
+    // Allocate stock
+    foreach ($items as $item) {
+        $size = isset($item['size']) ? $item['size'] : null;
+        allocateStock($item['product_id'], $item['quantity'], $size);
+    }
+    
+    // Create reservation
+    $now = new DateTime();
+    $expires = clone $now;
+    $expires->modify('+' . RESERVATION_EXPIRY_DAYS . ' days');
+    
+    $reservation = [
+        'id' => generateReservationId(),
+        'code' => generateReservationCode(),
+        'customer_name' => $customerName,
+        'customer_email' => $customerEmail,
+        'items' => $items,
+        'created' => $now->format('Y-m-d H:i:s'),
+        'expires' => $expires->format('Y-m-d H:i:s'),
+        'status' => 'open',
+        'picked_up' => false
+    ];
+    
+    $reservations[] = $reservation;
+    saveReservations($reservations);
+    
+    // Send confirmation emails
+    sendReservationEmails($reservation);
+    
+    return ['success' => true, 'reservation' => $reservation];
+}
+
+/**
+ * Mark reservation as picked up
+ */
+function markReservationPickedUp($reservationId) {
+    $reservations = getReservations();
+    foreach ($reservations as &$reservation) {
+        if ($reservation['id'] === $reservationId) {
+            $reservation['picked_up'] = true;
+            $reservation['status'] = 'picked_up';
+            break;
+        }
+    }
+    saveReservations($reservations);
+}
+
+/**
+ * Check and expire old reservations
+ */
+function expireOldReservations() {
+    $reservations = getReservations();
+    $now = new DateTime();
+    $changed = false;
+    
+    foreach ($reservations as &$reservation) {
+        if ($reservation['status'] === 'open' && !$reservation['picked_up']) {
+            $expires = new DateTime($reservation['expires']);
+            if ($now > $expires) {
+                $reservation['status'] = 'expired';
+                // Release stock
+                foreach ($reservation['items'] as $item) {
+                    $size = isset($item['size']) ? $item['size'] : null;
+                    releaseStock($item['product_id'], $item['quantity'], $size);
+                }
+                $changed = true;
+            }
+        }
+    }
+    
+    if ($changed) {
+        saveReservations($reservations);
+    }
+}
+
+/**
+ * Sanitize input
+ */
+function sanitize($input) {
+    return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
+}
+
+/**
+ * Format price
+ */
+function formatPrice($price) {
+    return number_format($price, 2, ',', '.') . ' €';
+}
+
+/**
+ * Format date
+ */
+function formatDate($dateString) {
+    $date = new DateTime($dateString);
+    return $date->format('d.m.Y H:i');
+}
+
+/**
+ * Send email
+ */
+function sendEmail($to, $subject, $message, $isHtml = true) {
+    $headers = [];
+    $headers[] = 'From: ' . FROM_NAME . ' <' . FROM_EMAIL . '>';
+    $headers[] = 'Reply-To: ' . FROM_EMAIL;
+    $headers[] = 'X-Mailer: PHP/' . phpversion();
+    
+    if ($isHtml) {
+        $headers[] = 'MIME-Version: 1.0';
+        $headers[] = 'Content-type: text/html; charset=UTF-8';
+    }
+    
+    return mail($to, $subject, $message, implode("\r\n", $headers));
+}
+
+/**
+ * Send reservation confirmation emails
+ */
+function sendReservationEmails($reservation) {
+    $products = getProducts();
+    
+    // Build items list
+    $itemsHtml = '<ul>';
+    foreach ($reservation['items'] as $item) {
+        $product = getProductById($item['product_id']);
+        if ($product) {
+            $sizeInfo = '';
+            if (isset($item['size']) && !empty($item['size'])) {
+                $sizeInfo = ' - Größe: ' . htmlspecialchars($item['size']);
+            }
+            $itemsHtml .= '<li>' . htmlspecialchars($product['name']) . $sizeInfo . ' - Menge: ' . $item['quantity'] . '</li>';
+        }
+    }
+    $itemsHtml .= '</ul>';
+    
+    // Customer email
+    $customerSubject = 'Ihre Reservierung bei ' . SITE_NAME;
+    $customerMessage = '
+    <html>
+    <head>
+        <meta charset="UTF-8">
+    </head>
+    <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
+        <h2 style="color: #c41e3a;">Reservierung bestätigt</h2>
+        <p>Sehr geehrte/r ' . htmlspecialchars($reservation['customer_name']) . ',</p>
+        <p>vielen Dank für Ihre Reservierung bei ' . SITE_NAME . '.</p>
+        
+        <div style="background: #fff3cd; border: 2px solid #ffc107; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px; text-align: center;">
+            <h3 style="margin-top: 0;">Ihr Abholcode:</h3>
+            <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #856404; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
+        </div>
+        
+        <h3>Reservierungsdetails:</h3>
+        <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
+        <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
+        <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
+        
+        <h3>Reservierte Artikel:</h3>
+        ' . $itemsHtml . '
+        
+        <p><strong>Wichtig:</strong> Bitte bringen Sie diesen Abholcode zur Abholung mit. Die Reservierung ist bis zum ' . formatDate($reservation['expires']) . ' gültig.</p>
+        
+        <p>Mit freundlichen Grüßen<br>' . SITE_NAME . '</p>
+    </body>
+    </html>';
+    
+    sendEmail($reservation['customer_email'], $customerSubject, $customerMessage);
+    
+    // Admin email
+    $adminSubject = 'Neue Reservierung: ' . $reservation['code'];
+    $adminMessage = '
+    <html>
+    <head>
+        <meta charset="UTF-8">
+    </head>
+    <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
+        <h2 style="color: #c41e3a;">Neue Reservierung</h2>
+        <p>Eine neue Reservierung wurde erstellt:</p>
+        
+        <div style="background: #d1ecf1; border: 2px solid #bee5eb; padding: 1.5rem; margin: 1.5rem 0; border-radius: 8px;">
+            <h3 style="margin-top: 0;">Abholcode:</h3>
+            <h2 style="font-size: 2rem; letter-spacing: 0.2rem; color: #0c5460; font-family: monospace;">' . htmlspecialchars($reservation['code']) . '</h2>
+        </div>
+        
+        <h3>Kundendaten:</h3>
+        <p><strong>Name:</strong> ' . htmlspecialchars($reservation['customer_name']) . '</p>
+        <p><strong>E-Mail:</strong> ' . htmlspecialchars($reservation['customer_email']) . '</p>
+        
+        <h3>Reservierungsdetails:</h3>
+        <p><strong>Reservierungsnummer:</strong> ' . htmlspecialchars($reservation['id']) . '</p>
+        <p><strong>Erstellt am:</strong> ' . formatDate($reservation['created']) . '</p>
+        <p><strong>Gültig bis:</strong> ' . formatDate($reservation['expires']) . '</p>
+        
+        <h3>Reservierte Artikel:</h3>
+        ' . $itemsHtml . '
+    </body>
+    </html>';
+    
+    sendEmail(ADMIN_EMAIL, $adminSubject, $adminMessage);
+}

+ 21 - 0
includes/header.php

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="de">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><?php echo isset($pageTitle) ? $pageTitle . ' - ' : ''; ?><?php echo SITE_NAME; ?></title>
+    <link rel="stylesheet" href="<?php echo SITE_URL; ?>/assets/css/style.css">
+</head>
+<body>
+    <header>
+        <div class="container">
+            <h1><?php echo SITE_NAME; ?></h1>
+            <nav>
+                <a href="<?php echo SITE_URL; ?>/index.php">Startseite</a>
+                <a href="<?php echo SITE_URL; ?>/cart.php">Warenkorb</a>
+                <a href="<?php echo SITE_URL; ?>/admin/">Admin</a>
+            </nav>
+        </div>
+    </header>
+    <main>
+        <div class="container">

+ 64 - 0
index.php

@@ -0,0 +1,64 @@
+<?php
+require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/includes/functions.php';
+
+$pageTitle = 'Startseite';
+$products = getProducts();
+
+// Filter by category if provided
+$category = isset($_GET['category']) ? sanitize($_GET['category']) : '';
+if ($category) {
+    $products = array_filter($products, function($product) use ($category) {
+        return $product['category'] === $category;
+    });
+}
+
+include __DIR__ . '/includes/header.php';
+?>
+
+<h2>Unsere Produkte</h2>
+
+<div style="margin: 1.5rem 0;">
+    <a href="?category=" class="btn btn-small <?php echo $category === '' ? '' : 'btn-secondary'; ?>">Alle</a>
+    <a href="?category=apparel" class="btn btn-small <?php echo $category === 'apparel' ? '' : 'btn-secondary'; ?>">Bekleidung</a>
+    <a href="?category=merch" class="btn btn-small <?php echo $category === 'merch' ? '' : 'btn-secondary'; ?>">Merchandise</a>
+</div>
+
+<?php if (empty($products)): ?>
+    <div class="alert alert-info">
+        <p>Keine Produkte gefunden.</p>
+    </div>
+<?php else: ?>
+    <div class="products-grid">
+        <?php foreach ($products as $product): ?>
+            <div class="product-card">
+                <a href="product.php?id=<?php echo $product['id']; ?>">
+                    <?php if (!empty($product['image']) && file_exists(__DIR__ . '/assets/images/' . $product['image'])): ?>
+                        <img src="<?php echo SITE_URL; ?>/assets/images/<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>">
+                    <?php else: ?>
+                        <img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='250' height='200'%3E%3Crect fill='%23e9ecef' width='250' height='200'/%3E%3Ctext x='50%25' y='50%25' text-anchor='middle' dy='.3em' fill='%236c757d'%3EKein Bild%3C/text%3E%3C/svg%3E" alt="Kein Bild">
+                    <?php endif; ?>
+                </a>
+                <div class="product-card-content">
+                    <h3><a href="product.php?id=<?php echo $product['id']; ?>" style="text-decoration: none; color: inherit;"><?php echo htmlspecialchars($product['name']); ?></a></h3>
+                    <div class="price"><?php echo formatPrice($product['price']); ?></div>
+                    <?php 
+                    $totalStock = getTotalStock($product);
+                    ?>
+                    <div class="stock <?php echo $totalStock > 0 ? 'in-stock' : 'out-of-stock'; ?>">
+                        <?php if ($totalStock > 0): ?>
+                            Verfügbar (<?php echo $totalStock; ?> Stück)
+                        <?php else: ?>
+                            Ausverkauft
+                        <?php endif; ?>
+                    </div>
+                    <?php if ($totalStock > 0): ?>
+                        <a href="product.php?id=<?php echo $product['id']; ?>" class="btn" style="width: 100%; text-align: center; margin-top: 1rem;">Details ansehen</a>
+                    <?php endif; ?>
+                </div>
+            </div>
+        <?php endforeach; ?>
+    </div>
+<?php endif; ?>
+
+<?php include __DIR__ . '/includes/footer.php'; ?>

+ 185 - 0
product.php

@@ -0,0 +1,185 @@
+<?php
+require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/includes/functions.php';
+
+$productId = isset($_GET['id']) ? (int)$_GET['id'] : 0;
+$product = getProductById($productId);
+
+if (!$product) {
+    header('Location: index.php');
+    exit;
+}
+
+$pageTitle = $product['name'];
+
+// Handle add to cart
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_to_cart'])) {
+    $quantity = isset($_POST['quantity']) ? (int)$_POST['quantity'] : 1;
+    $size = isset($_POST['size']) ? sanitize($_POST['size']) : '';
+    
+    // For apparel, size is required
+    if ($product['category'] === 'apparel' && empty($size)) {
+        $error = 'Bitte wählen Sie eine Größe aus.';
+    } elseif ($quantity < 1) {
+        $error = 'Menge muss mindestens 1 sein.';
+    } elseif (!checkStock($productId, $quantity, $size)) {
+        $sizeInfo = $size ? " für Größe $size" : '';
+        $error = "Nicht genügend Lagerbestand verfügbar$sizeInfo.";
+    } else {
+        // Add to session cart
+        if (!isset($_SESSION['cart'])) {
+            $_SESSION['cart'] = [];
+        }
+        
+        // For apparel, check if same product with same size already in cart
+        $found = false;
+        if ($product['category'] === 'apparel') {
+            foreach ($_SESSION['cart'] as &$item) {
+                if ($item['product_id'] == $productId && isset($item['size']) && $item['size'] === $size) {
+                    $item['quantity'] += $quantity;
+                    $found = true;
+                    break;
+                }
+            }
+        } else {
+            foreach ($_SESSION['cart'] as &$item) {
+                if ($item['product_id'] == $productId && !isset($item['size'])) {
+                    $item['quantity'] += $quantity;
+                    $found = true;
+                    break;
+                }
+            }
+        }
+        
+        if (!$found) {
+            $cartItem = [
+                'product_id' => $productId,
+                'quantity' => $quantity
+            ];
+            if ($product['category'] === 'apparel' && !empty($size)) {
+                $cartItem['size'] = $size;
+            }
+            $_SESSION['cart'][] = $cartItem;
+        }
+        
+        header('Location: cart.php');
+        exit;
+    }
+}
+
+include __DIR__ . '/includes/header.php';
+?>
+
+<?php if (isset($error)): ?>
+    <div class="alert alert-error">
+        <?php echo htmlspecialchars($error); ?>
+    </div>
+<?php endif; ?>
+
+<div class="product-detail-grid">
+    <div>
+        <?php if (!empty($product['image']) && file_exists(__DIR__ . '/assets/images/' . $product['image'])): ?>
+            <img src="<?php echo SITE_URL; ?>/assets/images/<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>" style="width: 100%; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
+        <?php else: ?>
+            <div style="width: 100%; height: 400px; background-color: #e9ecef; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #6c757d;">
+                Kein Bild verfügbar
+            </div>
+        <?php endif; ?>
+    </div>
+    
+    <div>
+        <h1><?php echo htmlspecialchars($product['name']); ?></h1>
+        <div class="price" style="font-size: 2rem; margin: 1rem 0;"><?php echo formatPrice($product['price']); ?></div>
+        
+        <?php 
+        $totalStock = getTotalStock($product);
+        $hasStock = $totalStock > 0;
+        ?>
+        <div class="stock <?php echo $hasStock ? 'in-stock' : 'out-of-stock'; ?>" style="font-size: 1.1rem; margin: 1rem 0;">
+            <?php if ($hasStock): ?>
+                <?php if ($product['category'] === 'apparel' && isset($product['stock_by_size']) && is_array($product['stock_by_size'])): ?>
+                    Verfügbar:
+                    <?php 
+                    $sizes = is_array($product['sizes']) ? $product['sizes'] : explode(',', $product['sizes']);
+                    $stockInfo = [];
+                    foreach ($sizes as $sizeOption) {
+                        $sizeOption = trim($sizeOption);
+                        $sizeStock = isset($product['stock_by_size'][$sizeOption]) ? (int)$product['stock_by_size'][$sizeOption] : 0;
+                        if ($sizeStock > 0) {
+                            $stockInfo[] = "$sizeOption: $sizeStock";
+                        }
+                    }
+                    echo implode(', ', $stockInfo);
+                    if (empty($stockInfo)) {
+                        echo 'Ausverkauft';
+                    }
+                    ?>
+                <?php else: ?>
+                    Verfügbar (<?php echo $totalStock; ?> Stück)
+                <?php endif; ?>
+            <?php else: ?>
+                Ausverkauft
+            <?php endif; ?>
+        </div>
+        
+        <div style="margin: 2rem 0;">
+            <h3>Beschreibung</h3>
+            <p style="margin-top: 0.5rem; line-height: 1.8;"><?php echo nl2br(htmlspecialchars($product['description'])); ?></p>
+        </div>
+        
+        <?php if ($hasStock): ?>
+            <form method="POST" style="margin-top: 2rem;">
+                <?php if ($product['category'] === 'apparel' && !empty($product['sizes'])): ?>
+                    <div class="form-group">
+                        <label for="size">Größe *</label>
+                        <select id="size" name="size" required style="width: 100%;" onchange="updateMaxQuantity()">
+                            <option value="">Bitte wählen</option>
+                            <?php 
+                            $sizes = is_array($product['sizes']) ? $product['sizes'] : explode(',', $product['sizes']);
+                            $stockBySize = isset($product['stock_by_size']) && is_array($product['stock_by_size']) ? $product['stock_by_size'] : [];
+                            foreach ($sizes as $sizeOption): 
+                                $sizeOption = trim($sizeOption);
+                                $sizeStock = isset($stockBySize[$sizeOption]) ? (int)$stockBySize[$sizeOption] : 0;
+                            ?>
+                                <option value="<?php echo htmlspecialchars($sizeOption); ?>" data-stock="<?php echo $sizeStock; ?>" <?php echo $sizeStock <= 0 ? 'disabled' : ''; ?>>
+                                    <?php echo htmlspecialchars($sizeOption); ?><?php echo $sizeStock <= 0 ? ' (Ausverkauft)' : " ($sizeStock verfügbar)"; ?>
+                                </option>
+                            <?php endforeach; ?>
+                        </select>
+                    </div>
+                <?php endif; ?>
+                <div class="form-group">
+                    <label for="quantity">Menge:</label>
+                    <input type="number" id="quantity" name="quantity" value="1" min="1" max="<?php echo $totalStock; ?>" class="quantity-input" style="width: 100px;">
+                </div>
+                <button type="submit" name="add_to_cart" class="btn" style="width: 100%;">In den Warenkorb</button>
+            </form>
+            <?php if ($product['category'] === 'apparel' && !empty($product['sizes'])): ?>
+            <script>
+                function updateMaxQuantity() {
+                    const sizeSelect = document.getElementById('size');
+                    const quantityInput = document.getElementById('quantity');
+                    const selectedOption = sizeSelect.options[sizeSelect.selectedIndex];
+                    if (selectedOption && selectedOption.value) {
+                        const stock = parseInt(selectedOption.getAttribute('data-stock')) || 0;
+                        quantityInput.max = stock;
+                        if (parseInt(quantityInput.value) > stock) {
+                            quantityInput.value = stock > 0 ? stock : 1;
+                        }
+                    }
+                }
+            </script>
+            <?php endif; ?>
+        <?php else: ?>
+            <div class="alert alert-error">
+                Dieses Produkt ist derzeit nicht verfügbar.
+            </div>
+        <?php endif; ?>
+        
+        <div style="margin-top: 2rem;">
+            <a href="index.php" class="btn btn-secondary">Zurück zur Übersicht</a>
+        </div>
+    </div>
+</div>
+
+<?php include __DIR__ . '/includes/footer.php'; ?>

+ 105 - 0
reservation.php

@@ -0,0 +1,105 @@
+<?php
+require_once __DIR__ . '/config.php';
+require_once __DIR__ . '/includes/functions.php';
+
+$pageTitle = 'Reservierung bestätigt';
+
+$code = isset($_GET['code']) ? sanitize($_GET['code']) : '';
+$reservation = null;
+
+if ($code) {
+    $reservation = getReservationByCode($code);
+}
+
+if (!$reservation) {
+    header('Location: index.php');
+    exit;
+}
+
+// Get product details for reservation items
+$items = [];
+foreach ($reservation['items'] as $item) {
+    $product = getProductById($item['product_id']);
+    if ($product) {
+        $items[] = [
+            'product' => $product,
+            'quantity' => $item['quantity'],
+            'size' => isset($item['size']) ? $item['size'] : null
+        ];
+    }
+}
+
+include __DIR__ . '/includes/header.php';
+?>
+
+<style media="print">
+    header, footer, .btn, nav {
+        display: none !important;
+    }
+    body {
+        padding: 20px;
+    }
+    .reservation-code {
+        page-break-inside: avoid;
+    }
+    table {
+        page-break-inside: avoid;
+    }
+</style>
+
+
+<div class="alert alert-success">
+    <h2>Reservierung erfolgreich!</h2>
+    <p>Vielen Dank für Ihre Reservierung, <?php echo htmlspecialchars($reservation['customer_name']); ?>.</p>
+</div>
+
+<div class="reservation-code">
+    <h3>Ihr Abholcode:</h3>
+    <h2><?php echo htmlspecialchars($reservation['code']); ?></h2>
+    <p>Bitte notieren Sie sich diesen Code und zeigen Sie ihn bei der Abholung vor.</p>
+</div>
+
+<div style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin: 2rem 0;">
+    <h3>Reservierungsdetails</h3>
+    
+    <p><strong>Reservierungsnummer:</strong> <?php echo htmlspecialchars($reservation['id']); ?></p>
+    <p><strong>Name:</strong> <?php echo htmlspecialchars($reservation['customer_name']); ?></p>
+    <p><strong>E-Mail:</strong> <?php echo htmlspecialchars($reservation['customer_email']); ?></p>
+    <p><strong>Erstellt am:</strong> <?php echo formatDate($reservation['created']); ?></p>
+    <p><strong>Gültig bis:</strong> <?php echo formatDate($reservation['expires']); ?></p>
+    
+    <h4 style="margin-top: 1.5rem;">Reservierte Artikel:</h4>
+    <table style="margin-top: 1rem;">
+        <thead>
+            <tr>
+                <th>Produkt</th>
+                <?php if (array_filter($items, function($i) { return !empty($i['size']); })): ?>
+                    <th>Größe</th>
+                <?php endif; ?>
+                <th>Menge</th>
+            </tr>
+        </thead>
+        <tbody>
+            <?php foreach ($items as $item): ?>
+                <tr>
+                    <td><?php echo htmlspecialchars($item['product']['name']); ?></td>
+                    <?php if (array_filter($items, function($i) { return !empty($i['size']); })): ?>
+                        <td><?php echo isset($item['size']) && !empty($item['size']) ? htmlspecialchars($item['size']) : '-'; ?></td>
+                    <?php endif; ?>
+                    <td><?php echo $item['quantity']; ?></td>
+                </tr>
+            <?php endforeach; ?>
+        </tbody>
+    </table>
+</div>
+
+<div class="alert alert-info">
+    <p><strong>Wichtig:</strong> Bitte bringen Sie diesen Abholcode zur Abholung mit. Die Reservierung ist bis zum <?php echo formatDate($reservation['expires']); ?> gültig.</p>
+</div>
+
+<div style="text-align: center; margin-top: 2rem;">
+    <button onclick="window.print()" class="btn">Reservierung drucken</button>
+    <a href="index.php" class="btn btn-secondary" style="margin-left: 1rem;">Zurück zur Startseite</a>
+</div>
+
+<?php include __DIR__ . '/includes/footer.php'; ?>