From 8cd81846c9a86013e6a284684b0d719cdda37607 Mon Sep 17 00:00:00 2001 From: Gemini Date: Wed, 13 May 2026 19:40:23 +0000 Subject: [PATCH] =?UTF-8?q?Fix:=20Wei=C3=9Fe=20Seite=20bei=20Direkteinstie?= =?UTF-8?q?g=20behoben=20(DB=20Auto-Migration=20in=20db=5Fconnect.php=20ve?= =?UTF-8?q?rschoben)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db_connect.php | 17 + src/index.php | 15 - src/packing_list_detail.php | 635 ++++++++++++++++++++++++++++++++++++ 3 files changed, 652 insertions(+), 15 deletions(-) diff --git a/src/db_connect.php b/src/db_connect.php index a565767..d70316f 100644 --- a/src/db_connect.php +++ b/src/db_connect.php @@ -39,4 +39,21 @@ $conn->set_charset("utf8mb4"); // Zeitzone setzen date_default_timezone_set('Europe/Berlin'); + +// --- AUTO MIGRATIONS --- +// Ensure display_name exists +$check_display_name = $conn->query("SHOW COLUMNS FROM users LIKE 'display_name'"); +if ($check_display_name && $check_display_name->num_rows == 0) { + $conn->query("ALTER TABLE users ADD COLUMN display_name VARCHAR(255) DEFAULT NULL"); +} + +// Ensure todo_lists and todo_items exist +$conn->query("CREATE TABLE IF NOT EXISTS todo_lists (id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, household_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"); +$conn->query("CREATE TABLE IF NOT EXISTS todo_items (id INT AUTO_INCREMENT PRIMARY KEY, todo_list_id INT NOT NULL, title VARCHAR(255) NOT NULL, is_completed TINYINT(1) DEFAULT 0, order_index INT DEFAULT 0, FOREIGN KEY (todo_list_id) REFERENCES todo_lists(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"); + +$check_todo_list_id = $conn->query("SHOW COLUMNS FROM packing_lists LIKE 'todo_list_id'"); +if ($check_todo_list_id && $check_todo_list_id->num_rows == 0) { + $conn->query("ALTER TABLE packing_lists ADD COLUMN todo_list_id INT DEFAULT NULL"); + $conn->query("ALTER TABLE packing_lists ADD CONSTRAINT fk_packing_list_todo FOREIGN KEY (todo_list_id) REFERENCES todo_lists(id) ON DELETE SET NULL"); +} ?> diff --git a/src/index.php b/src/index.php index 02a8b6c..4c0f224 100644 --- a/src/index.php +++ b/src/index.php @@ -13,21 +13,6 @@ if (!isset($_SESSION['user_id'])) { require_once 'db_connect.php'; -// --- AUTO MIGRATIONS --- -$check_display_name = $conn->query("SHOW COLUMNS FROM users LIKE 'display_name'"); -if ($check_display_name && $check_display_name->num_rows == 0) { - $conn->query("ALTER TABLE users ADD COLUMN display_name VARCHAR(255) DEFAULT NULL"); -} - -$conn->query("CREATE TABLE IF NOT EXISTS todo_lists (id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, household_id INT DEFAULT NULL, name VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (household_id) REFERENCES households(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"); -$conn->query("CREATE TABLE IF NOT EXISTS todo_items (id INT AUTO_INCREMENT PRIMARY KEY, todo_list_id INT NOT NULL, title VARCHAR(255) NOT NULL, is_completed TINYINT(1) DEFAULT 0, order_index INT DEFAULT 0, FOREIGN KEY (todo_list_id) REFERENCES todo_lists(id) ON DELETE CASCADE) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"); - -$check_todo_list_id = $conn->query("SHOW COLUMNS FROM packing_lists LIKE 'todo_list_id'"); -if ($check_todo_list_id && $check_todo_list_id->num_rows == 0) { - $conn->query("ALTER TABLE packing_lists ADD COLUMN todo_list_id INT DEFAULT NULL"); - $conn->query("ALTER TABLE packing_lists ADD CONSTRAINT fk_packing_list_todo FOREIGN KEY (todo_list_id) REFERENCES todo_lists(id) ON DELETE SET NULL"); -} - require_once 'header.php'; $current_user_id = $_SESSION['user_id']; diff --git a/src/packing_list_detail.php b/src/packing_list_detail.php index e69de29..4ce2381 100644 --- a/src/packing_list_detail.php +++ b/src/packing_list_detail.php @@ -0,0 +1,635 @@ +prepare("UPDATE todo_items SET is_completed = ? WHERE id = ?"); + $stmt->bind_param("ii", $status, $item_id); + $stmt->execute(); + $stmt->close(); + // Redirect to avoid form resubmission + header("Location: packing_list_detail.php?id=" . $packing_list_id); + exit; +} + +require_once 'header.php'; + +$packing_list = null; +$total_weight_grams = 0; +$total_consumable_weight = 0; +$weight_by_category = []; +$weight_by_carrier = []; +$weight_by_carrier_non_consumable = []; + +// Array for Carrier Detailed Stats (Modal) +$carrier_stats_details = []; + +if ($packing_list_id <= 0) { + die("Keine Packlisten-ID angegeben."); +} + +// Erweiterte Berechtigungsprüfung für Haushalte +$stmt_current_user = $conn->prepare("SELECT household_id FROM users WHERE id = ?"); +$stmt_current_user->bind_param("i", $current_user_id); +$stmt_current_user->execute(); +$current_user_household_id = $stmt_current_user->get_result()->fetch_assoc()['household_id']; +$stmt_current_user->close(); + +$stmt_list_owner = $conn->prepare( + "SELECT pl.*, u.household_id as owner_household_id + FROM packing_lists pl + JOIN users u ON pl.user_id = u.id + WHERE pl.id = ?" +); +$stmt_list_owner->bind_param("i", $packing_list_id); +$stmt_list_owner->execute(); +$result = $stmt_list_owner->get_result(); +if ($result->num_rows > 0) { + $packing_list = $result->fetch_assoc(); + $is_owner = ($packing_list['user_id'] == $current_user_id); + $is_household_list = !empty($packing_list['household_id']); + $is_in_same_household = ($is_household_list && $packing_list['household_id'] == $current_user_household_id); + + if (!$is_owner && !$is_in_same_household) { + die("Packliste nicht gefunden oder Zugriff verweigert."); + } +} else { + die("Packliste nicht gefunden oder Zugriff verweigert."); +} +$stmt_list_owner->close(); + +$todo_items = []; +$todo_list_name = ''; +if (!empty($packing_list['todo_list_id'])) { + $stmt_tl = $conn->prepare("SELECT name FROM todo_lists WHERE id = ?"); + $stmt_tl->bind_param("i", $packing_list['todo_list_id']); + $stmt_tl->execute(); + $res_tl = $stmt_tl->get_result(); + if ($row = $res_tl->fetch_assoc()) $todo_list_name = $row['name']; + $stmt_tl->close(); + + $stmt_td = $conn->prepare("SELECT * FROM todo_items WHERE todo_list_id = ? ORDER BY is_completed ASC, id ASC"); + $stmt_td->bind_param("i", $packing_list['todo_list_id']); + $stmt_td->execute(); + $todo_items = $stmt_td->get_result()->fetch_all(MYSQLI_ASSOC); + $stmt_td->close(); +} + +// Check for items on the table +$stmt_table = $conn->prepare("SELECT COUNT(*) as table_count FROM packing_list_items WHERE packing_list_id = ? AND carrier_user_id IS NULL AND backpack_id IS NULL AND backpack_compartment_id IS NULL AND parent_packing_list_item_id IS NULL"); +$stmt_table->bind_param("i", $packing_list_id); +$stmt_table->execute(); +$table_items_count = $stmt_table->get_result()->fetch_assoc()['table_count']; +$stmt_table->close(); + +$page_title = "Packliste: " . htmlspecialchars($packing_list['name']); + +// FIX: Join Categories also for Backpacks +$sql = "SELECT + pli.id, pli.quantity, pli.parent_packing_list_item_id, pli.carrier_user_id, + pli.backpack_id, pli.backpack_compartment_id, + COALESCE(a.name, pli.name, bp.name, bpc.name, 'Unbekanntes Item') AS article_name, + COALESCE(a.weight_grams, bp.weight_grams, 0) as weight_grams, + a.image_url, a.product_designation, a.consumable, + COALESCE(c.name, c_bp.name, 'Sonstiges') AS category_name, + m.name AS manufacturer_name, + COALESCE(NULLIF(u.display_name, ''), u.username) AS carrier_name, + u.id AS carrier_id + FROM packing_list_items AS pli + LEFT JOIN articles AS a ON pli.article_id = a.id + LEFT JOIN backpacks AS bp ON pli.backpack_id = bp.id + LEFT JOIN backpack_compartments AS bpc ON pli.backpack_compartment_id = bpc.id + LEFT JOIN categories AS c ON a.category_id = c.id + LEFT JOIN categories AS c_bp ON bp.category_id = c_bp.id + LEFT JOIN manufacturers AS m ON a.manufacturer_id = m.id + LEFT JOIN users AS u ON pli.carrier_user_id = u.id + WHERE pli.packing_list_id = ? + AND NOT (pli.carrier_user_id IS NULL AND pli.backpack_id IS NULL AND pli.backpack_compartment_id IS NULL AND pli.parent_packing_list_item_id IS NULL) + ORDER BY pli.order_index ASC"; + +$stmt = $conn->prepare($sql); +if (!$stmt) { + die("SQL Fehler: " . $conn->error); +} +$stmt->bind_param("i", $packing_list_id); +$stmt->execute(); +$result = $stmt->get_result(); + +$items_by_id = []; +$items_by_parent = []; + +while ($row = $result->fetch_assoc()) { + $items_by_id[$row['id']] = $row; + $parent_id = $row['parent_packing_list_item_id'] ?: 0; + if (!isset($items_by_parent[$parent_id])) { + $items_by_parent[$parent_id] = []; + } + $items_by_parent[$parent_id][] = $row; + + // Stats + $item_weight = $row['quantity'] * $row['weight_grams']; + $total_weight_grams += $item_weight; + + $carrier_name = $row['carrier_name'] ?: 'Sonstiges'; + $carrier_id = $row['carrier_id'] ?: 0; + + // Init stats arrays + if (!isset($weight_by_carrier[$carrier_name])) $weight_by_carrier[$carrier_name] = 0; + $weight_by_carrier[$carrier_name] += $item_weight; + + $cat_name = $row['category_name'] ?: 'Sonstiges'; + if (!isset($weight_by_category[$cat_name])) $weight_by_category[$cat_name] = 0; + $weight_by_category[$cat_name] += $item_weight; + + if ($row['consumable']) { + $total_consumable_weight += $item_weight; + } else { + if (!isset($weight_by_carrier_non_consumable[$carrier_name])) $weight_by_carrier_non_consumable[$carrier_name] = 0; + $weight_by_carrier_non_consumable[$carrier_name] += $item_weight; + } + + // Prepare Detailed Stats per Carrier for Modal + if (!isset($carrier_stats_details[$carrier_name])) { + $carrier_stats_details[$carrier_name] = [ + 'total_weight' => 0, + 'base_weight' => 0, + 'consumable_weight' => 0, + 'categories' => [] + ]; + } + $carrier_stats_details[$carrier_name]['total_weight'] += $item_weight; + if ($row['consumable']) { + $carrier_stats_details[$carrier_name]['consumable_weight'] += $item_weight; + } else { + $carrier_stats_details[$carrier_name]['base_weight'] += $item_weight; + } + if (!isset($carrier_stats_details[$carrier_name]['categories'][$cat_name])) { + $carrier_stats_details[$carrier_name]['categories'][$cat_name] = 0; + } + $carrier_stats_details[$carrier_name]['categories'][$cat_name] += $item_weight; +} +$stmt->close(); +$conn->close(); + +$total_weight_without_consumables = $total_weight_grams - $total_consumable_weight; + +// Helper functions (same as before) +function get_recursive_quantity($parent_id, $items_by_parent) { + $count = 0; + if (isset($items_by_parent[$parent_id])) { + foreach ($items_by_parent[$parent_id] as $child) { + $count += $child['quantity']; + $count += get_recursive_quantity($child['id'], $items_by_parent); + } + } + return $count; +} + +function get_recursive_weight($parent_id, $items_by_parent) { + $weight = 0; + if (isset($items_by_parent[$parent_id])) { + foreach ($items_by_parent[$parent_id] as $child) { + $weight += ($child['quantity'] * $child['weight_grams']); + $weight += get_recursive_weight($child['id'], $items_by_parent); + } + } + return $weight; +} + +function render_item_row($item, $level, $items_by_parent) { + $has_children = isset($items_by_parent[$item['id']]); + $is_backpack = !empty($item['backpack_id']); + $is_compartment = !empty($item['backpack_compartment_id']); + + $bg_class = ""; + $text_class = ""; + $icon = ""; + + if ($is_backpack) { + $bg_class = "table-success"; + $text_class = "fw-bold text-uppercase"; + $icon = ''; + } elseif ($is_compartment) { + $bg_class = "table-light"; + $text_class = "fw-bold fst-italic text-muted"; + // Check if linked article (image present) + if (!empty($item['image_url'])) { + $img_src = htmlspecialchars($item['image_url']); + $icon = ''; + } else { + $icon = ''; + } + } else { + $img_src = !empty($item['image_url']) ? htmlspecialchars($item['image_url']) : 'assets/images/keinbild.png'; + $icon = ''; + } + + $indent_px = $level * 25; + $weight_display = $item['weight_grams'] > 0 ? number_format($item['weight_grams'], 0, ',', '.') . ' g' : '-'; + + $total_weight_val = 0; + if ($is_backpack || $is_compartment) { + $children_weight = get_recursive_weight($item['id'], $items_by_parent); + $own_weight = $item['quantity'] * $item['weight_grams']; + $total_weight_val = $own_weight + $children_weight; + } else { + $total_weight_val = $item['weight_grams'] * $item['quantity']; + } + + $total_weight_display = ($total_weight_val > 0) ? number_format($total_weight_val, 0, ',', '.') . ' g' : '-'; + + echo ''; + + echo ''; + echo '
'; + + if ($has_children) { + echo ''; + } else { + echo ''; + } + + echo $icon; + echo '' . htmlspecialchars($item['article_name']) . ''; + echo '
'; + echo ''; + + echo '' . ($item['consumable'] ? '' : '') . ''; + echo '' . htmlspecialchars($item['manufacturer_name'] ?: '') . ''; + echo '' . htmlspecialchars($item['product_designation'] ?: '') . ''; + echo '' . htmlspecialchars($item['category_name'] ?: '') . ''; + + echo ''; + if ($is_backpack) { + echo ''; + } elseif ($is_compartment) { + $total_items = get_recursive_quantity($item['id'], $items_by_parent); + if ($total_items > 0) { + echo '' . $total_items . ''; + } + } else { + echo '' . $item['quantity'] . 'x'; + } + echo ''; + + echo '' . $weight_display . ''; + echo '' . $total_weight_display . ''; + + echo ''; + + if ($has_children) { + foreach ($items_by_parent[$item['id']] as $child) { + render_item_row($child, $level + 1, $items_by_parent); + } + } +} +?> + + + +
+
+
+

Packliste:

+ +
+
+ + 0): ?> + + + +
+
+
+
Gepackte Artikel
+
+
+ + + + + + + + + + + + + + + + + + $roots): ?> + + + + + + + +
Artikel / StrukturHerstellerModellKategorieAnz.GewichtGesamt
Liste ist leer.
+ + + + (Klicken für Statistik) + +
+
+
+
+
+
+ +
+
ToDo:
+
+
    + +
  • +
    + +
    + style="width:1.3em; height:1.3em; cursor:pointer;"> + + +
    +
    +
  • + + +
  • Keine Einträge in dieser Liste.
  • + +
+
+
+ + +
+
Statistiken
+
+
+
Gewicht pro Träger
+
    + $weight): ?> +
  • + +
    + Gesamt: g +
    +
    + Basis (o. Verbr.): g +
    +
  • + +
+
+
+
nach Kategorie
+
nach Träger
+
+ +
+
Gewicht nach Kategorie
+
+ + + $w): + ?> + + + + + + +
g
+
+
+
+
+
+
+
+ + + + +
+ + + + \ No newline at end of file