diff --git a/README.md b/README.md
index f7fd944..f6f4bcc 100644
--- a/README.md
+++ b/README.md
@@ -180,3 +180,7 @@ Das Projekt basiert auf bewährten Web-Standards:
* **Details:**
* Design-Update: Diagramme verwenden nun kontrastreiche Grüntöne passend zum Thema (ohne weiße Rahmen).
* Statistik: Klickbare Trägernamen öffnen ein Modal mit detaillierten Gewichtsstatistiken pro Kategorie.
+* **Modulare Rucksack-Erweiterung:**
+ * Fächer können nun direkt mit Artikeln verknüpft werden (z.B. "Hüftgurttasche").
+ * Diese "Zusatztaschen" werden in der Packliste automatisch als Container angelegt.
+ * Das Gewicht der Zusatztaschen wird automatisch zum Rucksack-Gesamtgewicht addiert.
diff --git a/src/backpacks.php b/src/backpacks.php
index be2f0ac..a58463c 100644
--- a/src/backpacks.php
+++ b/src/backpacks.php
@@ -48,7 +48,11 @@ $backpacks = [];
$check_col = $conn->query("SHOW COLUMNS FROM backpacks LIKE 'product_url'");
$has_product_url = ($check_col && $check_col->num_rows > 0);
-$sql = "SELECT b.*, u.username as owner_name
+$sql = "SELECT b.*, u.username as owner_name,
+ (SELECT SUM(a.weight_grams)
+ FROM backpack_compartments bc
+ JOIN articles a ON bc.linked_article_id = a.id
+ WHERE bc.backpack_id = b.id) as extra_weight
FROM backpacks b
JOIN users u ON b.user_id = u.id
WHERE b.user_id = ?";
@@ -85,7 +89,9 @@ while ($row = $result->fetch_assoc()) {
-
+
@@ -109,7 +115,13 @@ while ($row = $result->fetch_assoc()) {
- g
+
+
+ g
+ 0): ?>
+ (+)
+
+ L
diff --git a/src/edit_backpack.php b/src/edit_backpack.php
index d7553e6..5d17b08 100644
--- a/src/edit_backpack.php
+++ b/src/edit_backpack.php
@@ -25,11 +25,16 @@ $message = '';
$image_url = '';
$product_url = '';
-// AUTO-MIGRATION: Check if product_url column exists, if not add it
+// AUTO-MIGRATION: Check columns
$check_col = $conn->query("SHOW COLUMNS FROM backpacks LIKE 'product_url'");
if ($check_col && $check_col->num_rows == 0) {
$conn->query("ALTER TABLE backpacks ADD COLUMN product_url VARCHAR(255) DEFAULT NULL AFTER image_url");
}
+$check_col_c = $conn->query("SHOW COLUMNS FROM backpack_compartments LIKE 'linked_article_id'");
+if ($check_col_c && $check_col_c->num_rows == 0) {
+ $conn->query("ALTER TABLE backpack_compartments ADD COLUMN linked_article_id INT DEFAULT NULL");
+ $conn->query("ALTER TABLE backpack_compartments ADD CONSTRAINT fk_bc_article FOREIGN KEY (linked_article_id) REFERENCES articles(id) ON DELETE SET NULL");
+}
// Check Household
$household_id = null;
@@ -105,7 +110,29 @@ $stmt_man_load->execute();
$manufacturers = $stmt_man_load->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt_man_load->close();
-// Handle Form Submission BEFORE loading header
+// Load Articles for Linked Compartments
+// Filter: Show all articles (or maybe only containers/bags? User said "any"). Let's load all.
+$hh_ids = [$user_id];
+if ($household_id) {
+ // Get all users in household
+ $stmt_hhm = $conn->prepare("SELECT id FROM users WHERE household_id = ?");
+ $stmt_hhm->bind_param("i", $household_id);
+ $stmt_hhm->execute();
+ $res_hhm = $stmt_hhm->get_result();
+ while($r = $res_hhm->fetch_assoc()) $hh_ids[] = $r['id'];
+}
+$placeholders = implode(',', array_fill(0, count($hh_ids), '?'));
+$types_hh = str_repeat('i', count($hh_ids));
+$stmt_arts = $conn->prepare("SELECT id, name, weight_grams, image_url FROM articles WHERE user_id IN ($placeholders) OR household_id = ? ORDER BY name ASC");
+$params_arts = array_merge($hh_ids, [$household_id ?: 0]); // 0 if null
+$types_arts = $types_hh . 'i';
+$stmt_arts->bind_param($types_arts, ...$params_arts);
+$stmt_arts->execute();
+$all_articles = $stmt_arts->get_result()->fetch_all(MYSQLI_ASSOC);
+$stmt_arts->close();
+
+
+// Handle Form Submission
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$name = trim($_POST['name']);
@@ -115,7 +142,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($_POST['manufacturer_select'] === 'new') {
$new_man = trim($_POST['new_manufacturer_name']);
if (!empty($new_man)) {
- // Check if exists first
$stmt_check_man = $conn->prepare("SELECT id, name FROM manufacturers WHERE user_id = ? AND name = ?");
$stmt_check_man->bind_param("is", $user_id, $new_man);
$stmt_check_man->execute();
@@ -124,7 +150,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
if ($res_check->num_rows > 0) {
$manufacturer = $res_check->fetch_assoc()['name'];
} else {
- // Save to manufacturers table
$stmt_new_man = $conn->prepare("INSERT INTO manufacturers (name, user_id) VALUES (?, ?)");
$stmt_new_man->bind_param("si", $new_man, $user_id);
$stmt_new_man->execute();
@@ -132,7 +157,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
}
}
} else {
- // Look up name from ID
$man_id = intval($_POST['manufacturer_select']);
foreach ($manufacturers as $m) {
if ($m['id'] == $man_id) {
@@ -150,52 +174,25 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$product_url_input = trim($_POST['product_url'] ?? '');
// Image Handling
- $image_url_for_db = $image_url; // Keep existing by default
+ $image_url_for_db = $image_url;
$pasted_image = $_POST['pasted_image_data'] ?? '';
$url_image = trim($_POST['image_url_input'] ?? '');
if (!empty($pasted_image)) {
list($ok, $res) = save_image_from_base64($pasted_image, $upload_dir);
- if ($ok) {
- $image_url_for_db = $res;
- } else {
- $message .= '
Fehler beim Speichern des eingefügten Bildes: ' . htmlspecialchars($res) . '
Fehler beim Laden von URL: ' . htmlspecialchars($res) . '
';
- }
+ if ($ok) $image_url_for_db = $res;
}
$final_household_id = ($share_household && $household_id) ? $household_id : NULL;
@@ -214,11 +211,14 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
}
// Handle Compartments
- if (isset($_POST['compartment_names'])) {
- $comp_names = $_POST['compartment_names'];
- $comp_ids = $_POST['compartment_ids'] ?? [];
+ // Arrays: comp_ids, comp_types (text/article), comp_names, comp_articles
+ if (isset($_POST['comp_types'])) {
+ $types = $_POST['comp_types'];
+ $ids = $_POST['comp_ids'] ?? [];
+ $names = $_POST['comp_names'] ?? [];
+ $articles = $_POST['comp_articles'] ?? [];
- // Get existing IDs to know what to delete
+ // Get existing IDs
$existing_ids = [];
if($backpack_id > 0){
$stmt_check = $conn->prepare("SELECT id FROM backpack_compartments WHERE backpack_id = ?");
@@ -230,22 +230,37 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$kept_ids = [];
- for ($i = 0; $i < count($comp_names); $i++) {
- $c_name = trim($comp_names[$i]);
- $c_id = intval($comp_ids[$i] ?? 0);
-
- if (empty($c_name)) continue;
+ for ($i = 0; $i < count($types); $i++) {
+ $c_id = intval($ids[$i] ?? 0);
+ $c_type = $types[$i];
+ $c_name = '';
+ $c_article_id = NULL;
+
+ if ($c_type === 'article') {
+ $c_article_id = intval($articles[$i] ?? 0);
+ if ($c_article_id <= 0) continue; // Skip invalid
+ // Get name from article for display purposes (fallback)
+ foreach($all_articles as $art) {
+ if ($art['id'] == $c_article_id) {
+ $c_name = $art['name'];
+ break;
+ }
+ }
+ } else {
+ $c_name = trim($names[$i] ?? '');
+ if (empty($c_name)) continue;
+ }
if ($c_id > 0 && in_array($c_id, $existing_ids)) {
// Update
- $stmt_up = $conn->prepare("UPDATE backpack_compartments SET name = ?, sort_order = ? WHERE id = ?");
- $stmt_up->bind_param("sii", $c_name, $i, $c_id);
+ $stmt_up = $conn->prepare("UPDATE backpack_compartments SET name = ?, sort_order = ?, linked_article_id = ? WHERE id = ?");
+ $stmt_up->bind_param("siii", $c_name, $i, $c_article_id, $c_id);
$stmt_up->execute();
$kept_ids[] = $c_id;
} else {
// Insert
- $stmt_in = $conn->prepare("INSERT INTO backpack_compartments (backpack_id, name, sort_order) VALUES (?, ?, ?)");
- $stmt_in->bind_param("isi", $backpack_id, $c_name, $i);
+ $stmt_in = $conn->prepare("INSERT INTO backpack_compartments (backpack_id, name, sort_order, linked_article_id) VALUES (?, ?, ?, ?)");
+ $stmt_in->bind_param("isii", $backpack_id, $c_name, $i, $c_article_id);
$stmt_in->execute();
}
}
@@ -311,8 +326,9 @@ require_once 'header.php';
-
-
+
+
+
Ohne Zusatztaschen.
@@ -346,43 +362,75 @@ require_once 'header.php';
-
Fächeraufteilung
+
Fächer & Zusatztaschen
- Definiere hier die Bereiche deines Rucksacks (z.B. Deckelfach, Bodenfach). Diese erscheinen später in der Packliste als Container.
+ Definiere hier die Bereiche deines Rucksacks. Du kannst einfache Text-Fächer (z.B. "Deckelfach") oder echte Artikel als Zusatztaschen (z.B. "Hüftgurttasche") hinzufügen. Das Gewicht von Zusatztaschen wird zum Rucksackgewicht addiert.
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
Abbrechen
@@ -414,53 +462,93 @@ document.addEventListener('DOMContentLoaded', function() {
}
if (manSelect) {
- // Initial check
toggleManContainer(manSelect.value);
-
- // Vanilla Listener (Backup)
- manSelect.addEventListener('change', function() {
- toggleManContainer(this.value);
- });
- }
-
- // Tom Select Init
- if(manSelect) {
- new TomSelect(manSelect, {
- create: false,
- sortField: { field: "text", direction: "asc" },
- onChange: function(value) {
- toggleManContainer(value);
- }
- });
+ manSelect.addEventListener('change', function() { toggleManContainer(this.value); });
+ new TomSelect(manSelect, { create: false, sortField: { field: "text", direction: "asc" }, onChange: function(value) { toggleManContainer(value); } });
}
const container = document.getElementById('compartments-container');
- // Sortable for Compartments
new Sortable(container, {
- handle: '.input-group-text',
+ handle: '.handle',
animation: 150
});
+ // Template for new row (with placeholders)
+ // Note: We use a class 'tom-select-init' to mark selects that need init
+ const rowTemplate = `
+