diff --git a/README.md b/README.md index d75aa85..ac7a350 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,11 @@ Eine moderne, webbasierte Anwendung zur Verwaltung von Packlisten für Wanderung ## Funktionen * **Artikeldatenbank:** Erfasse deine gesamte Ausrüstung mit Bild, Gewicht, Hersteller und Kategorie. +* **Rucksack-Management:** Definiere deine Rucksäcke inkl. Fächern (Deckelfach, Bodenfach, etc.). * **Intelligente Packlisten:** * **Drag & Drop Editor:** Ziehe Artikel einfach aus deinem Bestand in die Liste. - * **Verschachtelung (Nesting):** Organisiere deine Liste logisch. Ziehe Socken *in* den Packsack und den Packsack *in* den Rucksack. + * **Container-Logik:** Weise Trägern Rucksäcke zu. Die Fächer erscheinen automatisch als Ordner, in die du packen kannst. + * **Verschachtelung (Nesting):** Organisiere deine Liste logisch (z.B. Erste-Hilfe-Set im Deckelfach). * **Haushalts-Modus:** Teile deinen Artikelbestand und deine Listen mit Familie oder Freunden. * **Gewichtskalkulation:** Sieh sofort, wie schwer dein Rucksack ist und wie viel Gewicht auf welche Kategorie (z.B. "Kochen", "Schlafen") entfällt. * **Druck-Optimierung:** Generiere eine übersichtliche PDF/Druckansicht zum Abhaken. @@ -38,14 +40,14 @@ Eine moderne, webbasierte Anwendung zur Verwaltung von Packlisten für Wanderung * MariaDB / MySQL Datenbank * PHP-Erweiterungen: `mysqli`, `gd` (für Bildbearbeitung) -### Schritt-für-Schritt +### Neuinstallation 1. **Dateien hochladen:** Kopiere alle Dateien aus diesem Repository in das Web-Verzeichnis deines Servers (z.B. `/var/www/html/packliste`). 2. **Datenbank einrichten:** * Erstelle eine leere Datenbank (z.B. `packliste`). - * Importiere die Datei `packliste.sql` in diese Datenbank (z.B. via phpMyAdmin oder Konsole). + * Importiere `packliste.sql` und anschließend `mysql_update.sql` (für die neuesten Features). 3. **Konfiguration:** * Erstelle eine Datei `config.ini` **außerhalb** des öffentlichen Web-Verzeichnisses (z.B. ein Verzeichnis höher als `index.php`). @@ -58,8 +60,10 @@ Eine moderne, webbasierte Anwendung zur Verwaltung von Packlisten für Wanderung ``` * *Hinweis:* Die Anwendung sucht standardmäßig unter `../config.ini`. -4. **Berechtigungen:** - Stelle sicher, dass der Webserver Schreibrechte auf den Ordner `uploads/` und `uploads/images/` hat. +### Upgrade (Vorhandene Installation) + +1. Führe das SQL-Skript `mysql_update.sql` auf deiner Datenbank aus. +2. Überschreibe die PHP-Dateien mit der neuesten Version. --- @@ -73,66 +77,40 @@ Nach der Registrierung landest du auf dem Dashboard. Die App ist darauf ausgeleg * **Mitglieder einladen:** Als Admin des Haushalts kannst du andere registrierte Benutzer per Benutzernamen einladen. Diese sehen nach dem Login eine Einladung, die sie annehmen können. * **Vorteil:** Alle Artikel, die als "Für Haushalt freigegeben" markiert sind, können von allen Mitgliedern in deren Packlisten verwendet werden. -### 2. Ausrüstung verwalten (Artikel) +### 2. Ausrüstung verwalten (Artikel & Rucksäcke) -Unter **"Artikel"** pflegst du deinen Bestand. +* **Artikel:** Pfleg deinen Bestand unter "Artikel". Du kannst Bilder hochladen, eine URL angeben oder Bilder per Copy & Paste einfügen. +* **Rucksäcke:** Unter **"Rucksäcke"** legst du deine Transportmittel an. Definiere hier auch die **Fächer** (z.B. Hauptfach, Deckelfach, Hüftgurt). Diese dienen später als Container in deinen Listen. -* **Erstellen:** Klicke auf "Neuen Artikel hinzufügen". -* **Bilder:** Du kannst Bilder hochladen, eine URL angeben oder (am Desktop sehr praktisch) ein Bild einfach per **Strg+V (Copy & Paste)** in das markierte Feld einfügen. -* **Eigenschaften:** - * **Gewicht:** Sei präzise! Das ist die Basis für die Statistiken. - * **Verbrauchsartikel:** Markiere Dinge wie Gas, Essen oder Sonnencreme. Diese haben in Packlisten standardmäßig die Menge 1, können aber beliebig erhöht werden. - * **Lagerort:** Hilft dir, das Zeug zu Hause wiederzufinden. -* **Listenansicht:** Die Artikelliste ist nach Kategorien gruppiert. Du kannst Kategorien ein- und ausklappen und oben über das Suchfeld blitzschnell filtern (z.B. nach "Zelt" oder "Merino"). - -### 3. Stammdaten (Kategorien, Orte, Hersteller) - -Damit alles ordentlich bleibt, solltest du die Stammdaten pflegen: - -* **Kategorien:** Definiere grobe Gruppen wie "Schlafen", "Küche", "Kleidung". -* **Lagerorte:** Hierarchisch aufgebaut (z.B. "Kellerregal" -> "Kiste 3"). -* **Hersteller:** Wird oft beim Artikelanlegen automatisch mit angelegt, kann hier aber bereinigt werden. - -### 4. Packlisten erstellen & bearbeiten +### 3. Packlisten erstellen & bearbeiten Dies ist das Herzstück der Anwendung. 1. Gehe zu **"Packlisten"** und erstelle eine neue Liste. -2. Klicke in der Übersicht auf **"Artikel verwalten"** (das Box-Icon) oder "Bearbeiten". +2. **Rucksack-Zuweisung:** Wähle direkt beim Erstellen (oder später unter "Details bearbeiten"), wer welchen Rucksack trägt. +3. Klicke in der Übersicht auf **"Artikel verwalten"** (das Box-Icon). #### Der Packlisten-Editor (Drag & Drop) Der Editor ist zweigeteilt: * **Links:** Dein verfügbarer Artikelbestand (filterbar). -* **Rechts:** Deine Packliste, gruppiert nach Trägern (Personen im Haushalt). +* **Rechts:** Deine Packliste, gruppiert nach Trägern und deren Rucksäcken/Fächern. **Bedienung:** -* **Hinzufügen:** Ziehe einen Artikel von links nach rechts in den Bereich einer Person. -* **Verschachteln (Nesting):** Das Killer-Feature! Du kannst Artikel **in** andere Artikel ziehen. - * *Beispiel:* Ziehe den "Schlafsack" auf den "Rucksack". Der Schlafsack ist nun logisch im Rucksack verpackt. - * Dies hilft enorm, die Übersicht zu behalten und beim Packen strukturiert vorzugehen. +* **Packen:** Ziehe Artikel in die entsprechenden Fächer deines Rucksacks. +* **Verschachteln:** Du kannst Artikel auch in andere Artikel ziehen (z.B. "Socken" in "Packbeutel"). * **Sortieren:** Ziehe Artikel nach oben oder unten, um die Reihenfolge zu ändern. -* **Menge:** Ändere die Anzahl direkt im Eingabefeld. -* **Komponenten:** Wenn du einen Artikel hinzufügst, der selbst Unter-Artikel im Bestand definiert hat (z.B. "Zelt" besteht aus "Zeltplane", "Gestänge", "Heringen"), fragt dich die App, ob diese Komponenten automatisch mit auf die Liste sollen. -### 5. Drucken & Export +### 4. Drucken & Export Klicke in der Packlisten-Übersicht auf **"Details ansehen"** (Augen-Icon). -* **Ansicht:** Du siehst eine hierarchische Baumstruktur deiner Liste. -* **Statistiken:** Rechts (oder mobil unten) siehst du Diagramme zur Gewichtsverteilung nach Kategorie und Träger. -* **Drucken:** Klicke auf "Drucken". Die Ansicht ist speziell für Papier optimiert: - * Farben und Bilder werden reduziert. - * Checkboxen zum manuellen Abhaken werden eingeblendet. - * Ideal als Checkliste für den Rucksack-Pack-Abend. +* **Ansicht:** Du siehst eine hierarchische Baumstruktur deiner Liste. Rucksäcke sind grün hinterlegt. +* **Drucken:** Klicke auf "Drucken". Die Ansicht ist speziell für Papier optimiert (fettgedruckte Fächer, Checkboxen für Artikel). -### 6. Profil & Einstellungen +### 5. Profil & Einstellungen -Unter **"Profil"** kannst du: -* Dein Passwort und Benutzernamen ändern. -* **App-Einstellungen:** - * *Tabellenanzeige:* Wie viele Items pro Seite? - * *Kategorien:* Sollen in der Artikelübersicht alle Kategorien standardmäßig eingeklappt sein? (Nützlich bei sehr vielen Artikeln). +Unter **"Profil"** kannst du dein Passwort ändern und App-Einstellungen vornehmen. --- diff --git a/add_packing_list.php b/add_packing_list.php deleted file mode 100644 index d21e539..0000000 --- a/add_packing_list.php +++ /dev/null @@ -1,110 +0,0 @@ -prepare("SELECT household_id FROM users WHERE id = ?"); - $stmt_household->bind_param("i", $current_user_id); - $stmt_household->execute(); - $household_id_for_user = $stmt_household->get_result()->fetch_assoc()['household_id']; - $stmt_household->close(); - - $name = trim($_POST['name']); - $description = trim($_POST['description']); - $household_id = isset($_POST['is_household_list']) && $household_id_for_user ? $household_id_for_user : NULL; - - if (empty($name)) { - $message = ''; - } else { - $stmt = $conn->prepare("INSERT INTO packing_lists (user_id, household_id, name, description) VALUES (?, ?, ?, ?)"); - if ($stmt === false) { - $message .= ''; - } else { - $stmt->bind_param("isss", $current_user_id, $household_id, $name, $description); - if ($stmt->execute()) { - $new_list_id = $conn->insert_id; - - if ($household_id_for_user) { - $log_message = htmlspecialchars($_SESSION['username']) . " hat die Packliste '" . htmlspecialchars($name) . "' erstellt."; - log_household_action($conn, $household_id_for_user, $current_user_id, $log_message); - } - - $_SESSION['message'] = ''; - header("Location: manage_packing_list_items.php?id=" . $new_list_id); - exit; // exit() ist wichtig nach einer header-Weiterleitung - } else { - $message .= ''; - } - $stmt->close(); - } - } -} - -// Die restlichen Daten werden nur für die Anzeige geladen, falls keine Weiterleitung erfolgt. -require_once 'header.php'; - -$stmt_household_display = $conn->prepare("SELECT household_id FROM users WHERE id = ?"); -$stmt_household_display->bind_param("i", $current_user_id); -$stmt_household_display->execute(); -$household_id_for_display = $stmt_household_display->get_result()->fetch_assoc()['household_id']; -$stmt_household_display->close(); - -$conn->close(); -?> - -
-
-

Neue Packliste erstellen

- Zur Übersicht -
-
- - -
-
- - -
-
- - -
Eine kurze Beschreibung, worum es bei dieser Packliste geht.
-
- - -
-
- - -
- - -
- -
- - Abbrechen -
-
-
-
- - diff --git a/edit_packing_list_details.php b/edit_packing_list_details.php deleted file mode 100644 index d71cc13..0000000 --- a/edit_packing_list_details.php +++ /dev/null @@ -1,133 +0,0 @@ - 0) { - $stmt_household_check = $conn->prepare("SELECT household_id FROM users WHERE id = ?"); - $stmt_household_check->bind_param("i", $current_user_id); - $stmt_household_check->execute(); - $current_user_household_id = $stmt_household_check->get_result()->fetch_assoc()['household_id']; - $stmt_household_check->close(); - - $stmt_list_check = $conn->prepare("SELECT id, name, description, user_id, household_id FROM packing_lists WHERE id = ?"); - $stmt_list_check->bind_param("i", $packing_list_id); - $stmt_list_check->execute(); - $result = $stmt_list_check->get_result(); - if ($result->num_rows == 1) { - $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) { - $can_edit = true; - } else { - $message = ''; - $packing_list = null; - } - } else { - $message = ''; - } - $stmt_list_check->close(); -} else { - $message = ''; -} - -if ($_SERVER["REQUEST_METHOD"] == "POST" && $can_edit) { - $name = trim($_POST['name']); - $description = trim($_POST['description']); - if (empty($name)) { - $message = ''; - } else { - $stmt_update = $conn->prepare("UPDATE packing_lists SET name = ?, description = ? WHERE id = ?"); - if ($stmt_update) { - $stmt_update->bind_param("ssi", $name, $description, $packing_list_id); - if ($stmt_update->execute()) { - if ($stmt_update->affected_rows > 0) { - if ($current_user_household_id) { - $log_message = htmlspecialchars($_SESSION['username']) . " hat die Packliste '" . htmlspecialchars($name) . "' bearbeitet."; - log_household_action($conn, $current_user_household_id, $current_user_id, $log_message); - } - $message = ''; - $packing_list['name'] = $name; - $packing_list['description'] = $description; - } else { - $message = ''; - } - } else { - $message = ''; - } - $stmt_update->close(); - } - } -} -$conn->close(); -?> - -
-
-

Details bearbeiten:

- Zur Übersicht -
-
- - - -
-
- - -
-
- - -
- Eine kurze Beschreibung oder Notizen zu dieser Packliste. -
-
- -
- -
- -
- - Artikel verwalten - - - Zurück zur Ansicht - -
-
-
- - - -
-
- - diff --git a/mysql_update.sql b/mysql_update.sql new file mode 100644 index 0000000..1ca1252 --- /dev/null +++ b/mysql_update.sql @@ -0,0 +1,51 @@ +-- Datenbank-Update: Rucksack-Feature +-- Führe dieses Skript aus, um eine bestehende Installation zu aktualisieren. + +-- 1. Neue Tabelle für Rucksäcke +CREATE TABLE IF NOT EXISTS backpacks ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + household_id INT DEFAULT NULL, + name VARCHAR(255) NOT NULL, + manufacturer VARCHAR(255), + model VARCHAR(255), + weight_grams INT DEFAULT 0, + volume_liters INT DEFAULT 0, + image_url VARCHAR(255), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + UNIQUE KEY unique_backpack_name (user_id, household_id, name) +); + +-- 2. Neue Tabelle für Rucksack-Fächer +CREATE TABLE IF NOT EXISTS backpack_compartments ( + id INT AUTO_INCREMENT PRIMARY KEY, + backpack_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + sort_order INT DEFAULT 0, + FOREIGN KEY (backpack_id) REFERENCES backpacks(id) ON DELETE CASCADE +); + +-- 3. Neue Tabelle für die Zuweisung von Rucksäcken zu Trägern pro Liste +CREATE TABLE IF NOT EXISTS packing_list_carriers ( + id INT AUTO_INCREMENT PRIMARY KEY, + packing_list_id INT NOT NULL, + user_id INT NOT NULL, + backpack_id INT DEFAULT NULL, + FOREIGN KEY (packing_list_id) REFERENCES packing_lists(id) ON DELETE CASCADE, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (backpack_id) REFERENCES backpacks(id) ON DELETE SET NULL +); + +-- 4. Anpassung der packing_list_items Tabelle +-- Hinzufügen der Spalten für Rucksack-Referenzen und freie Namen (für Container) +ALTER TABLE packing_list_items ADD COLUMN IF NOT EXISTS backpack_id INT DEFAULT NULL; +ALTER TABLE packing_list_items ADD COLUMN IF NOT EXISTS backpack_compartment_id INT DEFAULT NULL; +ALTER TABLE packing_list_items ADD COLUMN IF NOT EXISTS name VARCHAR(255) DEFAULT NULL; + +-- Foreign Keys setzen +ALTER TABLE packing_list_items ADD CONSTRAINT fk_pli_backpack FOREIGN KEY (backpack_id) REFERENCES backpacks(id) ON DELETE SET NULL; +ALTER TABLE packing_list_items ADD CONSTRAINT fk_pli_compartment FOREIGN KEY (backpack_compartment_id) REFERENCES backpack_compartments(id) ON DELETE SET NULL; + +-- WICHTIG: article_id muss NULL sein dürfen (für reine Container-Items) +ALTER TABLE packing_list_items MODIFY COLUMN article_id INT NULL; diff --git a/packing_list_detail.php b/packing_list_detail.php deleted file mode 100644 index cf3f889..0000000 --- a/packing_list_detail.php +++ /dev/null @@ -1,361 +0,0 @@ -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(); - $owner_household_id = $packing_list['owner_household_id']; - - $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(); - -$page_title = "Packliste: " . htmlspecialchars($packing_list['name']); - -$sql = "SELECT pli.id, pli.quantity, pli.parent_packing_list_item_id, a.name AS article_name, a.weight_grams, a.image_url, a.product_designation, a.consumable, c.name AS category_name, m.name AS manufacturer_name, u.username AS carrier_name, l2.name AS location_level2_name, l1.name AS location_level1_name FROM packing_list_items AS pli JOIN articles AS a ON pli.article_id = a.id LEFT JOIN categories AS c ON a.category_id = c.id LEFT JOIN manufacturers AS m ON a.manufacturer_id = m.id LEFT JOIN users AS u ON pli.carrier_user_id = u.id LEFT JOIN storage_locations l2 ON a.storage_location_id = l2.id LEFT JOIN storage_locations l1 ON l2.parent_id = l1.id WHERE pli.packing_list_id = ?"; -$stmt = $conn->prepare($sql); -if ($stmt === false) { die('SQL-Vorbereitung fehlgeschlagen: ' . htmlspecialchars($conn->error)); } -$stmt->bind_param("i", $packing_list_id); -$stmt->execute(); -$result = $stmt->get_result(); -while ($row = $result->fetch_assoc()) { - $all_items[$row['id']] = $row; - $item_total_weight = $row['quantity'] * $row['weight_grams']; - $total_weight_grams += $item_total_weight; - $category_name = $row['category_name'] ?: 'Ohne Kategorie'; - $carrier_name = $row['carrier_name'] ?: 'Sonstiges'; - @$weight_by_category[$category_name] += $item_total_weight; - @$weight_by_carrier[$carrier_name] += $item_total_weight; - if ($row['consumable']) { - $total_consumable_weight += $item_total_weight; - } else { - @$weight_by_carrier_non_consumable[$carrier_name] += $item_total_weight; - } -} -$stmt->close(); -$conn->close(); - -$total_weight_without_consumables = $total_weight_grams - $total_consumable_weight; - -$items_by_carrier_hierarchical = []; -foreach($all_items as $item) { - $carrier_name = $item['carrier_name'] ?: 'Sonstiges'; - $items_by_carrier_hierarchical[$carrier_name][$item['id']] = $item; -} -foreach($items_by_carrier_hierarchical as &$carrier_items) { - $hierarchical_list = []; - foreach ($carrier_items as $id => &$item) { - if (!isset($item['children'])) { - $item['children'] = []; - } - if (empty($item['parent_packing_list_item_id']) || !isset($carrier_items[$item['parent_packing_list_item_id']])) { - $hierarchical_list[$id] = &$item; - } else { - $carrier_items[$item['parent_packing_list_item_id']]['children'][] = &$item; - } - } - $carrier_items = $hierarchical_list; -} -unset($carrier_items, $item); - -$sorted_weight_by_carrier = []; -$temp_carrier_weights = $weight_by_carrier; -$current_username = $_SESSION['username']; -if (isset($temp_carrier_weights[$current_username])) { - $sorted_weight_by_carrier[$current_username] = $temp_carrier_weights[$current_username]; - unset($temp_carrier_weights[$current_username]); -} -$unassigned_weight = null; -if (isset($temp_carrier_weights['Sonstiges'])) { - $unassigned_weight = $temp_carrier_weights['Sonstiges']; - unset($temp_carrier_weights['Sonstiges']); -} -ksort($temp_carrier_weights); -$sorted_weight_by_carrier += $temp_carrier_weights; -if ($unassigned_weight !== null) { - $sorted_weight_by_carrier['Sonstiges'] = $unassigned_weight; -} -$sorted_items_by_carrier = array_replace(array_flip(array_keys($sorted_weight_by_carrier)), $items_by_carrier_hierarchical); - -function render_screen_rows($items, $level = 0, $parent_id = null) { - foreach ($items as $item) { - $item_weight = $item['quantity'] * $item['weight_grams']; - $has_children = !empty($item['children']); - $image_path = !empty($item['image_url']) ? htmlspecialchars($item['image_url']) : 'keinbild.png'; - - $row_class = 'level-' . $level; - $parent_attr = $parent_id ? 'data-parent-id="' . $parent_id . '"' : ''; - - echo ''; - echo '' . htmlspecialchars($item['article_name']) . ''; - - echo ''; - echo '
'; - echo ''; - - if ($has_children) { - echo ''; - } else { - echo ''; - } - - 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 '' . $item['quantity'] . 'x'; - echo '' . number_format($item['weight_grams'], 0, ',', '.') . ' g'; - echo '' . number_format($item_weight, 0, ',', '.') . ' g'; - echo ''; - - if ($has_children) render_screen_rows($item['children'], $level + 1, $item['id']); - } -} - -function render_print_table_rows($items, $level = 0) { - foreach($items as $item) { - $location_string = !empty($item['location_level1_name']) ? $item['location_level1_name'] . ' > ' . $item['location_level2_name'] : '---'; - echo ''; - echo ''; - echo '
' . ($level > 0 ? '' : '') . htmlspecialchars($item['article_name']) . '
'; - echo '' . htmlspecialchars($item['manufacturer_name'] ?: '---') . ''; - echo '' . htmlspecialchars($item['product_designation'] ?: '---') . ''; - echo '' . htmlspecialchars($location_string) . ''; - echo '' . $item['quantity'] . 'x'; - echo ''; - if (!empty($item['children'])) { - render_print_table_rows($item['children'], $level + 1); - } - } -} -?> - - - -
-
-
-

Packliste:

- -
-
-
-
-
-
Gepackte Artikel
-
-
- - - - - - - $items): ?> - - - - - - - - - - -
BildArtikelHerstellerModell/TypKategorieMengeGewicht/Stk.G-Gewicht
Diese Packliste ist leer.
-
-
-
-
-
-
-
Statistiken
-
-
-
Gewicht pro Träger
-
    - $weight): ?> -
  • Gesamt: g
    Basisgewicht: g
  • - -
-
-
-
nach Kategorie
-
nach Träger
-
-
-
$w): ?>
g
-
-
-
-
-
-
- - - -
- - - \ No newline at end of file diff --git a/setup_backpacks.php b/setup_backpacks.php new file mode 100644 index 0000000..c1f6ea2 --- /dev/null +++ b/setup_backpacks.php @@ -0,0 +1,45 @@ +query($sql) === TRUE) { + echo "Table created successfully\n"; + } else { + echo "Error creating table: " . $conn->error . "\n"; + } +} + +$conn->close(); +?> diff --git a/add_article.php b/src/add_article.php old mode 100644 new mode 100755 similarity index 100% rename from add_article.php rename to src/add_article.php diff --git a/src/add_packing_list.php b/src/add_packing_list.php new file mode 100755 index 0000000..2844411 --- /dev/null +++ b/src/add_packing_list.php @@ -0,0 +1,192 @@ +prepare("SELECT household_id FROM users WHERE id = ?"); +$stmt_household->bind_param("i", $current_user_id); +$stmt_household->execute(); +$household_id_for_user = $stmt_household->get_result()->fetch_assoc()['household_id']; +$stmt_household->close(); + +// Fetch Users for Backpack Assignment UI +$available_users = []; +if ($household_id_for_user) { + $stmt_u = $conn->prepare("SELECT id, username FROM users WHERE household_id = ?"); + $stmt_u->bind_param("i", $household_id_for_user); +} else { + // Just the current user + $stmt_u = $conn->prepare("SELECT id, username FROM users WHERE id = ?"); + $stmt_u->bind_param("i", $current_user_id); +} +$stmt_u->execute(); +$res_u = $stmt_u->get_result(); +while ($r = $res_u->fetch_assoc()) $available_users[] = $r; +$stmt_u->close(); + + +// Handle Form Submission +if ($_SERVER["REQUEST_METHOD"] == "POST") { + $name = trim($_POST['name']); + $description = trim($_POST['description']); + $household_id = isset($_POST['is_household_list']) && $household_id_for_user ? $household_id_for_user : NULL; + + if (empty($name)) { + $message = ''; + } else { + $stmt = $conn->prepare("INSERT INTO packing_lists (user_id, household_id, name, description) VALUES (?, ?, ?, ?)"); + if ($stmt === false) { + $message .= ''; + } else { + $stmt->bind_param("isss", $current_user_id, $household_id, $name, $description); + if ($stmt->execute()) { + $new_list_id = $conn->insert_id; + + // Handle Backpacks + if (isset($_POST['backpacks'])) { + foreach ($_POST['backpacks'] as $uid => $bid) { + $uid = intval($uid); + $bid = intval($bid); + if ($bid > 0) { + $stmt_in = $conn->prepare("INSERT INTO packing_list_carriers (packing_list_id, user_id, backpack_id) VALUES (?, ?, ?)"); + $stmt_in->bind_param("iii", $new_list_id, $uid, $bid); + $stmt_in->execute(); + $stmt_in->close(); + + sync_backpack_items($conn, $new_list_id, $uid, $bid); + } + } + } + + if ($household_id_for_user) { + $log_message = htmlspecialchars($_SESSION['username']) . " hat die Packliste '" . htmlspecialchars($name) . "' erstellt."; + log_household_action($conn, $household_id_for_user, $current_user_id, $log_message); + } + + $_SESSION['message'] = ''; + header("Location: manage_packing_list_items.php?id=" . $new_list_id); + exit; + } else { + $message .= ''; + } + $stmt->close(); + } + } +} + +require_once 'header.php'; +?> + +
+
+

Neue Packliste erstellen

+ Zur Übersicht +
+
+ + +
+
+
+
+ + +
+
+ + +
Eine kurze Beschreibung, worum es bei dieser Packliste geht.
+
+ + +
+ + +
+ +
+
+
Rucksack-Zuweisung
+
+
+

Wähle gleich hier, wer welchen Rucksack trägt.

+ + +
+ + +
+ +
+
+
+
+ +
+ +
+ + Abbrechen +
+
+
+ +
+ + diff --git a/api_packing_list_handler.php b/src/api_packing_list_handler.php old mode 100644 new mode 100755 similarity index 61% rename from api_packing_list_handler.php rename to src/api_packing_list_handler.php index 89825f0..5df171a --- a/api_packing_list_handler.php +++ b/src/api_packing_list_handler.php @@ -69,7 +69,7 @@ try { if(!$stmt_delete->execute()) throw new Exception("Fehler beim Löschen der alten Liste."); $stmt_delete->close(); - $stmt_insert = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, article_id, quantity, carrier_user_id, order_index, parent_packing_list_item_id) VALUES (?, ?, ?, ?, ?, ?)"); + $stmt_insert = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, article_id, quantity, carrier_user_id, order_index, parent_packing_list_item_id, backpack_id, backpack_compartment_id, name) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); $id_map = []; // Mappt temporäre Frontend-IDs auf neue DB-IDs foreach ($items_from_frontend as $index => $item_data) { @@ -77,6 +77,26 @@ try { $article_id = intval($item_data['article_id']); $carrier_id = ($item_data['carrier_id'] === 'null') ? NULL : intval($item_data['carrier_id']); + // New fields + $backpack_id = !empty($item_data['backpack_id']) ? intval($item_data['backpack_id']) : NULL; + $backpack_compartment_id = !empty($item_data['backpack_compartment_id']) ? intval($item_data['backpack_compartment_id']) : NULL; + // For normal items, name is NULL (fetched from articles). For containers, name might be set if needed, but usually containers are created via edit_packing_list_details. + // If this sync receives items that are containers (backpack_id set), we might not have 'name' in payload if it came from DOM. + // Actually, the containers are created in 'edit_details'. Sync just re-orders them. + // We need to preserve the 'name' if it exists in the old record, or pass it from frontend. + // Simplified: If article_id is 0 or null, we might need a name. + // BUT: The frontend sends 'article_id' from dataset. Containers created in backend have article_id=0 (or NULL). + // Let's assume for now we just persist what we get. If article_id is missing, we rely on DB to handle it (it's nullable?). + // Wait, the previous INSERT required article_id. We need to change article_id column to be NULLable or handle 0. + + $article_id_val = ($article_id > 0) ? $article_id : NULL; + $name_val = NULL; // Only needed if we were passing names from frontend, which we aren't really for existing items. + + // Re-fetch name for containers if we are re-inserting them? + // The sync deletes and re-inserts. If we don't provide a name for a container (which has no article_id), it will be lost! + // We MUST fetch the name from the frontend payload or the old DB record. + // Let's adding 'name' to payload in frontend is best. + // WICHTIG: Parent-ID muss bereits gemappt sein. Das Frontend MUSS Eltern VOR Kindern senden. // Die rekursive "traverse"-Funktion im JS garantiert dies (Pre-Order Traversal). $parent_pli_id = isset($item_data['parent_pli_id']) && isset($id_map[$item_data['parent_pli_id']]) ? $id_map[$item_data['parent_pli_id']] : null; @@ -84,7 +104,7 @@ try { $quantity = isset($old_quantities[$pli_id_frontend]) ? intval($old_quantities[$pli_id_frontend]) : 1; if ($quantity < 1) $quantity = 1; - $stmt_insert->bind_param("iiiiii", $packing_list_id, $article_id, $quantity, $carrier_id, $index, $parent_pli_id); + $stmt_insert->bind_param("iiiiiiiis", $packing_list_id, $article_id_val, $quantity, $carrier_id, $index, $parent_pli_id, $backpack_id, $backpack_compartment_id, $name_val); if(!$stmt_insert->execute()) throw new Exception("Fehler beim Einfügen von Artikel " . $article_id . ": " . $stmt_insert->error); $new_db_id = $conn->insert_id; $id_map[$pli_id_frontend] = $new_db_id; @@ -108,21 +128,56 @@ try { foreach ($child_items as $child) { $temp_index++; - $stmt_insert->bind_param("iiiiii", $packing_list_id, $child['id'], 1, $carrier_id, $temp_index, $new_db_id); + // Child items are normal items + $stmt_insert->bind_param("iiiiiiiis", $packing_list_id, $child['id'], 1, $carrier_id, $temp_index, $new_db_id, $null_val, $null_val, $null_val); + $null_val = NULL; if(!$stmt_insert->execute()) throw new Exception("Fehler beim Einfügen von Kind-Artikel " . $child['id']); } } } } $stmt_insert->close(); + + // Post-Fix: Restore names for containers from Backpack tables if they were lost because we didn't send them from frontend. + // Actually, better to just UPDATE them based on IDs after insertion. + // Update Root Names + $conn->query("UPDATE packing_list_items pli JOIN backpacks b ON pli.backpack_id = b.id SET pli.name = CONCAT('Rucksack: ', b.name) WHERE pli.packing_list_id = $packing_list_id AND pli.backpack_id IS NOT NULL"); + // Update Compartment Names + $conn->query("UPDATE packing_list_items pli JOIN backpack_compartments bc ON pli.backpack_compartment_id = bc.id SET pli.name = bc.name WHERE pli.packing_list_id = $packing_list_id AND pli.backpack_compartment_id IS NOT NULL"); + break; case 'delete_item': $item_id = intval($data['item_id']); - $stmt_update_children = $conn->prepare("UPDATE packing_list_items SET parent_packing_list_item_id = NULL WHERE parent_packing_list_item_id = ?"); - $stmt_update_children->bind_param("i", $item_id); $stmt_update_children->execute(); $stmt_update_children->close(); - $stmt_delete = $conn->prepare("DELETE FROM packing_list_items WHERE id = ? AND packing_list_id = ?"); - $stmt_delete->bind_param("ii", $item_id, $packing_list_id); $stmt_delete->execute(); $stmt_delete->close(); + + // Recursive Delete Logic using CTE if MySQL 8+ or manual recursion + // Manual recursion to be safe on older DBs: + $ids_to_delete = [$item_id]; + $i = 0; + while($i < count($ids_to_delete)) { + $current_parent = $ids_to_delete[$i]; + $stmt_children = $conn->prepare("SELECT id FROM packing_list_items WHERE parent_packing_list_item_id = ?"); + $stmt_children->bind_param("i", $current_parent); + $stmt_children->execute(); + $result = $stmt_children->get_result(); + while($row = $result->fetch_assoc()) { + $ids_to_delete[] = $row['id']; + } + $stmt_children->close(); + $i++; + } + + // Delete all gathered IDs + $in_query = implode(',', array_fill(0, count($ids_to_delete), '?')); + $types = str_repeat('i', count($ids_to_delete)); + $stmt_delete = $conn->prepare("DELETE FROM packing_list_items WHERE id IN ($in_query) AND packing_list_id = ?"); + + // Combine ID params and list ID + $delete_params = array_merge($ids_to_delete, [$packing_list_id]); + $delete_types = $types . 'i'; + $stmt_delete->bind_param($delete_types, ...$delete_params); + $stmt_delete->execute(); + $stmt_delete->close(); break; case 'update_quantity': $item_id = intval($data['item_id']); @@ -169,7 +224,7 @@ function user_can_edit_list($conn, $packing_list_id, $user_id) { } function get_all_items($conn, $packing_list_id) { - $stmt = $conn->prepare("SELECT pli.id, pli.article_id, pli.quantity, pli.parent_packing_list_item_id, pli.carrier_user_id, a.name, a.weight_grams, a.product_designation, a.consumable, m.name as manufacturer_name FROM packing_list_items pli JOIN articles a ON pli.article_id = a.id LEFT JOIN manufacturers m ON a.manufacturer_id = m.id WHERE pli.packing_list_id = ? ORDER BY pli.order_index ASC, pli.id ASC"); + $stmt = $conn->prepare("SELECT pli.id, pli.article_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) as name, a.weight_grams, a.product_designation, a.consumable, m.name as manufacturer_name FROM packing_list_items pli LEFT JOIN articles a ON pli.article_id = a.id LEFT JOIN manufacturers m ON a.manufacturer_id = m.id WHERE pli.packing_list_id = ? ORDER BY pli.order_index ASC, pli.id ASC"); $stmt->bind_param("i", $packing_list_id); $stmt->execute(); $result = $stmt->get_result()->fetch_all(MYSQLI_ASSOC); diff --git a/articles.php b/src/articles.php old mode 100644 new mode 100755 similarity index 100% rename from articles.php rename to src/articles.php diff --git a/assets/css/style.css b/src/assets/css/style.css old mode 100644 new mode 100755 similarity index 98% rename from assets/css/style.css rename to src/assets/css/style.css index ffb6494..4ef2d41 --- a/assets/css/style.css +++ b/src/assets/css/style.css @@ -750,9 +750,22 @@ body::before { padding: 0.75rem; } -.toggle-tree-btn { - color: var(--color-secondary); - transition: color 0.2s; +.save-feedback { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background-color: var(--color-primary-dark); + color: #fff; + padding: 10px 20px; + border-radius: 30px; + box-shadow: 0 4px 15px rgba(0,0,0,0.2); + z-index: 9999; + opacity: 0; + transition: opacity 0.3s ease-in-out; + pointer-events: none; + font-weight: 600; + font-size: 0.9rem; } /* --- Articles Table Styles --- */ /* Sticky Header */ diff --git a/src/backpack_utils.php b/src/backpack_utils.php new file mode 100644 index 0000000..9000edf --- /dev/null +++ b/src/backpack_utils.php @@ -0,0 +1,73 @@ +query("SELECT name FROM backpacks WHERE id = $backpack_id")->fetch_assoc(); + + // 2. Check/Create Root Item + // Use NULL safe comparison or check for NULL explicitly + // We need to allow for existing items that might be named differently if user renamed them? + // No, we stick to structure. + $root_id = 0; + $stmt = $conn->prepare("SELECT id FROM packing_list_items WHERE packing_list_id = ? AND carrier_user_id = ? AND backpack_id = ?"); + $stmt->bind_param("iii", $list_id, $user_id, $backpack_id); + $stmt->execute(); + $res = $stmt->get_result(); + + if ($row = $res->fetch_assoc()) { + $root_id = $row['id']; + } else { + // Create Root + $name = "Rucksack: " . $bp['name']; + // Use correct column count and NULLs + $stmt_ins = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, carrier_user_id, name, backpack_id, quantity, article_id, order_index, parent_packing_list_item_id) VALUES (?, ?, ?, ?, 1, NULL, 0, NULL)"); + $stmt_ins->bind_param("iisi", $list_id, $user_id, $name, $backpack_id); + $stmt_ins->execute(); + $root_id = $stmt_ins->insert_id; + } + + // 3. Sync Compartments + $comps = $conn->query("SELECT id, name FROM backpack_compartments WHERE backpack_id = $backpack_id ORDER BY sort_order ASC"); + while ($comp = $comps->fetch_assoc()) { + // Check if item exists for this compartment AND this user + $stmt_c = $conn->prepare("SELECT id FROM packing_list_items WHERE packing_list_id = ? AND backpack_compartment_id = ? AND carrier_user_id = ?"); + $stmt_c->bind_param("iii", $list_id, $comp['id'], $user_id); + $stmt_c->execute(); + if ($stmt_c->get_result()->num_rows == 0) { + // Create Compartment Item + $c_name = $comp['name']; + $stmt_ins_c = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, carrier_user_id, name, backpack_compartment_id, parent_packing_list_item_id, quantity, article_id, order_index) VALUES (?, ?, ?, ?, ?, 1, NULL, 0)"); + $stmt_ins_c->bind_param("iisii", $list_id, $user_id, $c_name, $comp['id'], $root_id); + $stmt_ins_c->execute(); + } + } +} + +// Helper to get backpacks for a specific user context +function get_available_backpacks_for_user($conn, $target_user_id, $household_id) { + // Check connection + if (!$conn || $conn->connect_errno) { + // Reconnect or fail gracefully? + // For now, assume caller must keep it open. + return []; + } + + $bps = []; + $sql_bp = "SELECT id, name, user_id FROM backpacks WHERE user_id = ?"; + if ($household_id) { + $sql_bp .= " OR household_id = ?"; + $stmt_bp = $conn->prepare($sql_bp); + $stmt_bp->bind_param("ii", $target_user_id, $household_id); + } else { + $stmt_bp = $conn->prepare($sql_bp); + $stmt_bp->bind_param("i", $target_user_id); + } + $stmt_bp->execute(); + $res_bp = $stmt_bp->get_result(); + while ($row = $res_bp->fetch_assoc()) { + $bps[] = $row; + } + return $bps; +} +?> \ No newline at end of file diff --git a/src/backpacks.php b/src/backpacks.php new file mode 100755 index 0000000..105352c --- /dev/null +++ b/src/backpacks.php @@ -0,0 +1,137 @@ +prepare("SELECT id FROM backpacks WHERE id = ? AND user_id = ?"); + $stmt->bind_param("ii", $delete_id, $user_id); + $stmt->execute(); + if ($stmt->get_result()->num_rows > 0) { + $stmt_del = $conn->prepare("DELETE FROM backpacks WHERE id = ?"); + $stmt_del->bind_param("i", $delete_id); + if ($stmt_del->execute()) { + $message = '
Rucksack gelöscht.
'; + } else { + $message = '
Fehler beim Löschen: ' . $conn->error . '
'; + } + } else { + $message = '
Keine Berechtigung.
'; + } +} + +// Fetch Backpacks (Personal + Household) +// Logic: Show my backpacks AND backpacks from my household (if I'm in one) +$household_id = null; +$stmt_hh = $conn->prepare("SELECT household_id FROM users WHERE id = ?"); +$stmt_hh->bind_param("i", $user_id); +$stmt_hh->execute(); +$res_hh = $stmt_hh->get_result(); +if ($row = $res_hh->fetch_assoc()) { + $household_id = $row['household_id']; +} + +$backpacks = []; +$sql = "SELECT b.*, u.username as owner_name + FROM backpacks b + JOIN users u ON b.user_id = u.id + WHERE b.user_id = ?"; + +if ($household_id) { + $sql .= " OR (b.household_id = ?)"; +} +$sql .= " ORDER BY b.name ASC"; + +$stmt = $conn->prepare($sql); +if ($household_id) { + $stmt->bind_param("ii", $user_id, $household_id); +} else { + $stmt->bind_param("i", $user_id); +} +$stmt->execute(); +$result = $stmt->get_result(); +while ($row = $result->fetch_assoc()) { + $backpacks[] = $row; +} + +?> + +
+
+

Rucksäcke

+ Neuen Rucksack anlegen +
+
+ + + +
+ Du hast noch keine Rucksäcke definiert. Lege jetzt deinen ersten Rucksack an! +
+ +
+ +
+
+
+
+
+ + Meiner + + von + +
+ +

+ +

+ +
+ g + L +
+ + + prepare("SELECT COUNT(*) as cnt FROM backpack_compartments WHERE backpack_id = ?"); + $stmt_c->bind_param("i", $bp['id']); + $stmt_c->execute(); + $cnt = $stmt_c->get_result()->fetch_assoc()['cnt']; + ?> +

Fächer definiert

+ +
+ +
+
+ +
+ +
+
+ + diff --git a/categories.php b/src/categories.php old mode 100644 new mode 100755 similarity index 100% rename from categories.php rename to src/categories.php diff --git a/db_connect.php b/src/db_connect.php old mode 100644 new mode 100755 similarity index 100% rename from db_connect.php rename to src/db_connect.php diff --git a/delete_article.php b/src/delete_article.php old mode 100644 new mode 100755 similarity index 100% rename from delete_article.php rename to src/delete_article.php diff --git a/delete_packing_list.php b/src/delete_packing_list.php old mode 100644 new mode 100755 similarity index 100% rename from delete_packing_list.php rename to src/delete_packing_list.php diff --git a/duplicate_packing_list.php b/src/duplicate_packing_list.php old mode 100644 new mode 100755 similarity index 100% rename from duplicate_packing_list.php rename to src/duplicate_packing_list.php diff --git a/edit_article.php b/src/edit_article.php old mode 100644 new mode 100755 similarity index 100% rename from edit_article.php rename to src/edit_article.php diff --git a/src/edit_backpack.php b/src/edit_backpack.php new file mode 100755 index 0000000..4f1f92b --- /dev/null +++ b/src/edit_backpack.php @@ -0,0 +1,249 @@ +prepare("SELECT household_id FROM users WHERE id = ?"); +$stmt_hh->bind_param("i", $user_id); +$stmt_hh->execute(); +$res_hh = $stmt_hh->get_result(); +if ($row = $res_hh->fetch_assoc()) { + $household_id = $row['household_id']; +} + +// Handle Form Submission BEFORE loading header +if ($_SERVER['REQUEST_METHOD'] == 'POST') { + $name = trim($_POST['name']); + $manufacturer = trim($_POST['manufacturer']); + $model = trim($_POST['model']); + $weight = intval($_POST['weight_grams']); + $volume = intval($_POST['volume_liters']); + $share_household = isset($_POST['share_household']) ? 1 : 0; + + $final_household_id = ($share_household && $household_id) ? $household_id : NULL; + + if ($backpack_id > 0) { + // Update + $stmt = $conn->prepare("UPDATE backpacks SET name=?, manufacturer=?, model=?, weight_grams=?, volume_liters=?, household_id=? WHERE id=? AND user_id=?"); + $stmt->bind_param("sssiisii", $name, $manufacturer, $model, $weight, $volume, $final_household_id, $backpack_id, $user_id); + $stmt->execute(); + } else { + // Insert + $stmt = $conn->prepare("INSERT INTO backpacks (user_id, household_id, name, manufacturer, model, weight_grams, volume_liters) VALUES (?, ?, ?, ?, ?, ?, ?)"); + $stmt->bind_param("iisssii", $user_id, $final_household_id, $name, $manufacturer, $model, $weight, $volume); + $stmt->execute(); + $backpack_id = $stmt->insert_id; + } + + // Handle Compartments + if (isset($_POST['compartment_names'])) { + $comp_names = $_POST['compartment_names']; + $comp_ids = $_POST['compartment_ids'] ?? []; + + // Get existing IDs to know what to delete + // We need to re-fetch existing just for this logic if not loaded yet, or just assume based on IDs + // Since we haven't loaded existing yet in this flow, let's just operate on IDs. + + // First load existing IDs for safety to check ownership indirectly via backpack_id + $existing_ids = []; + $stmt_check = $conn->prepare("SELECT id FROM backpack_compartments WHERE backpack_id = ?"); + $stmt_check->bind_param("i", $backpack_id); + $stmt_check->execute(); + $res_check = $stmt_check->get_result(); + while($row = $res_check->fetch_assoc()) $existing_ids[] = $row['id']; + + $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; + + 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->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->execute(); + } + } + + // Delete removed + foreach ($existing_ids as $ex_id) { + if (!in_array($ex_id, $kept_ids)) { + $conn->query("DELETE FROM backpack_compartments WHERE id = $ex_id"); + } + } + } + + header("Location: backpacks.php"); + exit; +} + +require_once 'header.php'; + +// Load existing data (Moved after header inclusion is fine for display logic, but data loading could be before too) +if ($backpack_id > 0) { + $stmt = $conn->prepare("SELECT * FROM backpacks WHERE id = ? AND user_id = ?"); + $stmt->bind_param("ii", $backpack_id, $user_id); + $stmt->execute(); + $result = $stmt->get_result(); + if ($result->num_rows > 0) { + $backpack = $result->fetch_assoc(); + + // Load Compartments + $stmt_c = $conn->prepare("SELECT * FROM backpack_compartments WHERE backpack_id = ? ORDER BY sort_order ASC"); + $stmt_c->bind_param("i", $backpack_id); + $stmt_c->execute(); + $res_c = $stmt_c->get_result(); + while ($row = $res_c->fetch_assoc()) { + $compartments[] = $row; + } + } else { + $message = '
Rucksack nicht gefunden oder keine Berechtigung.
'; + $backpack_id = 0; // Reset to create mode + } +} +?> + +?> + +
+
+
+
+
+

0 ? 'Rucksack bearbeiten' : 'Neuen Rucksack anlegen'; ?>

+
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ > + +
+
+
+ +
Fächeraufteilung
+
+ Definiere hier die Bereiche deines Rucksacks (z.B. Deckelfach, Bodenfach). Diese erscheinen später in der Packliste als Container. +
+ +
+ + +
+ + + + +
+
+ + + + +
+ + +
+ + + + +
+ + +
+ + + +
+ Abbrechen + +
+
+
+
+
+
+
+ + + + + diff --git a/src/edit_packing_list_details.php b/src/edit_packing_list_details.php new file mode 100755 index 0000000..70b3fd1 --- /dev/null +++ b/src/edit_packing_list_details.php @@ -0,0 +1,232 @@ + 0) { + $stmt_household_check = $conn->prepare("SELECT household_id FROM users WHERE id = ?"); + $stmt_household_check->bind_param("i", $current_user_id); + $stmt_household_check->execute(); + $current_user_household_id = $stmt_household_check->get_result()->fetch_assoc()['household_id']; + $stmt_household_check->close(); + + $stmt_list_check = $conn->prepare("SELECT id, name, description, user_id, household_id FROM packing_lists WHERE id = ?"); + $stmt_list_check->bind_param("i", $packing_list_id); + $stmt_list_check->execute(); + $result = $stmt_list_check->get_result(); + if ($result->num_rows == 1) { + $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) { + $can_edit = true; + } else { + $message = '
Keine Berechtigung.
'; + $packing_list = null; + } + } else { + $message = '
Packliste nicht gefunden.
'; + } +} else { + $message = '
Keine ID.
'; +} + +// --- 2. Fetch Data for Dropdowns --- +$available_users = []; +if ($can_edit) { + // Owners: Creator + Household Members (if shared) + if ($packing_list['household_id']) { + $stmt = $conn->prepare("SELECT id, username FROM users WHERE household_id = ?"); + $stmt->bind_param("i", $packing_list['household_id']); + } else { + $stmt = $conn->prepare("SELECT id, username FROM users WHERE id = ?"); + $stmt->bind_param("i", $packing_list['user_id']); + } + $stmt->execute(); + $res = $stmt->get_result(); + while ($row = $res->fetch_assoc()) { + $available_users[] = $row; + } + + // Current Assignments + $current_assignments = []; + $stmt_ca = $conn->prepare("SELECT user_id, backpack_id FROM packing_list_carriers WHERE packing_list_id = ?"); + $stmt_ca->bind_param("i", $packing_list_id); + $stmt_ca->execute(); + $res_ca = $stmt_ca->get_result(); + while ($row = $res_ca->fetch_assoc()) { + $current_assignments[$row['user_id']] = $row['backpack_id']; + } +} + +// --- 3. Handle Form Submission --- +if ($_SERVER["REQUEST_METHOD"] == "POST" && $can_edit) { + // Update Basic Details + $name = trim($_POST['name']); + $description = trim($_POST['description']); + $stmt_update = $conn->prepare("UPDATE packing_lists SET name = ?, description = ? WHERE id = ?"); + $stmt_update->bind_param("ssi", $name, $description, $packing_list_id); + $stmt_update->execute(); + $packing_list['name'] = $name; + $packing_list['description'] = $description; + + // Handle Backpack Assignments + if (isset($_POST['backpacks'])) { + foreach ($_POST['backpacks'] as $uid => $bid) { + $uid = intval($uid); + $bid = intval($bid); + $bid = ($bid > 0) ? $bid : NULL; + + // Update Carrier Table + // Check if exists + $old_backpack_id = 0; + $stmt_chk = $conn->prepare("SELECT id, backpack_id FROM packing_list_carriers WHERE packing_list_id = ? AND user_id = ?"); + $stmt_chk->bind_param("ii", $packing_list_id, $uid); + $stmt_chk->execute(); + $res_chk = $stmt_chk->get_result(); + if ($row_chk = $res_chk->fetch_assoc()) { + $old_backpack_id = $row_chk['backpack_id']; + $stmt_up = $conn->prepare("UPDATE packing_list_carriers SET backpack_id = ? WHERE packing_list_id = ? AND user_id = ?"); + $stmt_up->bind_param("iii", $bid, $packing_list_id, $uid); + $stmt_up->execute(); + } else { + $stmt_in = $conn->prepare("INSERT INTO packing_list_carriers (packing_list_id, user_id, backpack_id) VALUES (?, ?, ?)"); + $stmt_in->bind_param("iii", $packing_list_id, $uid, $bid); + $stmt_in->execute(); + } + + // CLEANUP LOGIC: If backpack changed or removed + if ($old_backpack_id != $bid) { + // 1. Unparent all items that are inside the old containers (so they don't get deleted) + // Find all container items for this user + $stmt_find_containers = $conn->prepare("SELECT id FROM packing_list_items WHERE packing_list_id = ? AND carrier_user_id = ? AND (backpack_id IS NOT NULL OR backpack_compartment_id IS NOT NULL)"); + $stmt_find_containers->bind_param("ii", $packing_list_id, $uid); + $stmt_find_containers->execute(); + $res_cont = $stmt_find_containers->get_result(); + $container_ids = []; + while ($r = $res_cont->fetch_assoc()) $container_ids[] = $r['id']; + + if (!empty($container_ids)) { + $ids_str = implode(',', $container_ids); + // Set parent to NULL for children of these containers + $conn->query("UPDATE packing_list_items SET parent_packing_list_item_id = NULL WHERE packing_list_id = $packing_list_id AND parent_packing_list_item_id IN ($ids_str)"); + + // 2. Delete the containers + $conn->query("DELETE FROM packing_list_items WHERE id IN ($ids_str)"); + } + } + + // SYNC LOGIC (Only if new backpack assigned) + if ($bid && $old_backpack_id != $bid) { + sync_backpack_items($conn, $packing_list_id, $uid, $bid); + } + } + } + + $message = '
Änderungen gespeichert!
'; + // Refresh assignments + $stmt_ca->execute(); + $res_ca = $stmt_ca->get_result(); + $current_assignments = []; + while ($row = $res_ca->fetch_assoc()) { + $current_assignments[$row['user_id']] = $row['backpack_id']; + } +} + +?> + +
+
+

Details:

+ Zurück +
+
+ + + +
+
+
+
Basisdaten
+
+ + +
+
+ + +
+
+ +
+
Rucksack-Zuweisung
+
+
+

Wähle hier, wer welchen Rucksack trägt. Bereits vergebene Rucksäcke werden ausgeblendet.

+ + +
+ + +
+ +
+
+
+
+ +
+ +
+ Inhalt bearbeiten + +
+
+ +
+
+ + \ No newline at end of file diff --git a/footer.php b/src/footer.php old mode 100644 new mode 100755 similarity index 100% rename from footer.php rename to src/footer.php diff --git a/header.php b/src/header.php old mode 100644 new mode 100755 similarity index 97% rename from header.php rename to src/header.php index ec7a4ad..2e92098 --- a/header.php +++ b/src/header.php @@ -53,6 +53,7 @@ if (isset($_SESSION['user_id'])) {