Feature: Packlisten-Templates hinzugefügt
Some checks failed
Docker Build & Push / build-and-push (push) Failing after 15s
Some checks failed
Docker Build & Push / build-and-push (push) Failing after 15s
- DB-Schema erweitert (is_template Spalte in packing_lists) - packing_lists.php: Tabs für Listen/Vorlagen und Filterung - save_as_template.php: Funktion zum Speichern einer Liste als Vorlage - add_packing_list.php: Neue Liste aus Vorlage erstellen - packing_list_utils.php: Zentrale Kopier-Logik (inkl. Träger & Hierarchien) - edit_packing_list_details.php: Navigation angepasst
This commit is contained in:
@@ -402,6 +402,7 @@ CREATE TABLE `packing_lists` (
|
||||
`name` varchar(255) NOT NULL,
|
||||
`description` text DEFAULT NULL,
|
||||
`share_token` varchar(255) DEFAULT NULL,
|
||||
`is_template` tinyint(1) NOT NULL DEFAULT 0,
|
||||
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `share_token` (`share_token`),
|
||||
|
||||
@@ -13,6 +13,7 @@ if (!isset($_SESSION['user_id'])) {
|
||||
require_once 'db_connect.php';
|
||||
require_once 'household_actions.php';
|
||||
require_once 'backpack_utils.php';
|
||||
require_once 'packing_list_utils.php';
|
||||
|
||||
$current_user_id = $_SESSION['user_id'];
|
||||
$message = '';
|
||||
@@ -26,6 +27,23 @@ $stmt_household->execute();
|
||||
$household_id_for_user = $stmt_household->get_result()->fetch_assoc()['household_id'];
|
||||
$stmt_household->close();
|
||||
|
||||
// Fetch available templates
|
||||
$templates = [];
|
||||
$sql_templates = "SELECT id, name FROM packing_lists WHERE is_template = 1 AND (user_id = ? OR household_id = ?)";
|
||||
$stmt_templates = $conn->prepare($sql_templates);
|
||||
// Falls household_id NULL ist, binden wir trotzdem User ID als Dummy oder handeln es anders.
|
||||
// Einfacher: Wir nutzen current_user_id zweimal, wenn household NULL, oder Logik im SQL.
|
||||
// Da household_id INT ist, kann es NULL sein.
|
||||
// Besser:
|
||||
$h_id = $household_id_for_user ?: 0; // 0 matches nothing usually, safe
|
||||
$stmt_templates->bind_param("ii", $current_user_id, $h_id);
|
||||
$stmt_templates->execute();
|
||||
$res_templates = $stmt_templates->get_result();
|
||||
while ($row = $res_templates->fetch_assoc()) {
|
||||
$templates[] = $row;
|
||||
}
|
||||
$stmt_templates->close();
|
||||
|
||||
// Fetch Users for Backpack Assignment UI
|
||||
$available_users = [];
|
||||
if ($household_id_for_user) {
|
||||
@@ -45,21 +63,25 @@ $stmt_u->close();
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$name = trim($_POST['name']);
|
||||
$description = trim($_POST['description']);
|
||||
$template_id = isset($_POST['template_id']) ? intval($_POST['template_id']) : 0;
|
||||
$household_id = isset($_POST['is_household_list']) && $household_id_for_user ? $household_id_for_user : NULL;
|
||||
|
||||
// Server-Side Validation for duplicate backpacks
|
||||
$selected_backpacks = [];
|
||||
// Server-Side Validation for duplicate backpacks (ONLY if no template is selected)
|
||||
$has_duplicate_backpacks = false;
|
||||
if (isset($_POST['participate']) && is_array($_POST['participate'])) {
|
||||
foreach ($_POST['participate'] as $uid => $val) {
|
||||
if ($val) {
|
||||
$bid = isset($_POST['backpacks'][$uid]) ? intval($_POST['backpacks'][$uid]) : 0;
|
||||
if ($bid > 0) {
|
||||
if (in_array($bid, $selected_backpacks)) {
|
||||
$has_duplicate_backpacks = true;
|
||||
break;
|
||||
|
||||
if ($template_id == 0) {
|
||||
$selected_backpacks = [];
|
||||
if (isset($_POST['participate']) && is_array($_POST['participate'])) {
|
||||
foreach ($_POST['participate'] as $uid => $val) {
|
||||
if ($val) {
|
||||
$bid = isset($_POST['backpacks'][$uid]) ? intval($_POST['backpacks'][$uid]) : 0;
|
||||
if ($bid > 0) {
|
||||
if (in_array($bid, $selected_backpacks)) {
|
||||
$has_duplicate_backpacks = true;
|
||||
break;
|
||||
}
|
||||
$selected_backpacks[] = $bid;
|
||||
}
|
||||
$selected_backpacks[] = $bid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +92,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
} elseif ($has_duplicate_backpacks) {
|
||||
$message = '<div class="alert alert-danger" role="alert">Fehler: Ein Rucksack kann nicht mehreren Personen zugewiesen werden. Bitte korrigieren Sie die Auswahl.</div>';
|
||||
} else {
|
||||
$stmt = $conn->prepare("INSERT INTO packing_lists (user_id, household_id, name, description) VALUES (?, ?, ?, ?)");
|
||||
$stmt = $conn->prepare("INSERT INTO packing_lists (user_id, household_id, name, description, is_template) VALUES (?, ?, ?, ?, 0)");
|
||||
if ($stmt === false) {
|
||||
$message .= '<div class="alert alert-danger" role="alert">SQL Prepare-Fehler: ' . $conn->error . '</div>';
|
||||
} else {
|
||||
@@ -78,23 +100,37 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
if ($stmt->execute()) {
|
||||
$new_list_id = $conn->insert_id;
|
||||
|
||||
if (isset($_POST['participate']) && is_array($_POST['participate'])) {
|
||||
foreach ($_POST['participate'] as $uid => $val) {
|
||||
$uid = intval($uid);
|
||||
if ($val) {
|
||||
$bid = isset($_POST['backpacks'][$uid]) ? intval($_POST['backpacks'][$uid]) : 0;
|
||||
$bid_to_insert = ($bid > 0) ? $bid : NULL;
|
||||
|
||||
$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_to_insert);
|
||||
$stmt_in->execute();
|
||||
$stmt_in->close();
|
||||
|
||||
if ($bid > 0) {
|
||||
sync_backpack_items($conn, $new_list_id, $uid, $bid);
|
||||
if ($template_id > 0) {
|
||||
// Option A: Aus Template erstellen
|
||||
try {
|
||||
copyPackingListContent($template_id, $new_list_id, $conn);
|
||||
$_SESSION['message'] = '<div class="alert alert-success" role="alert">Packliste erfolgreich aus Vorlage erstellt!</div>';
|
||||
} catch (Exception $e) {
|
||||
// Rollback oder Warnung? Wir lassen die Liste da, aber warnen.
|
||||
$_SESSION['message'] = '<div class="alert alert-warning" role="alert">Liste erstellt, aber Fehler beim Kopieren der Vorlage: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
|
||||
} else {
|
||||
// Option B: Leere Liste mit manuellen Trägern
|
||||
if (isset($_POST['participate']) && is_array($_POST['participate'])) {
|
||||
foreach ($_POST['participate'] as $uid => $val) {
|
||||
$uid = intval($uid);
|
||||
if ($val) {
|
||||
$bid = isset($_POST['backpacks'][$uid]) ? intval($_POST['backpacks'][$uid]) : 0;
|
||||
$bid_to_insert = ($bid > 0) ? $bid : NULL;
|
||||
|
||||
$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_to_insert);
|
||||
$stmt_in->execute();
|
||||
$stmt_in->close();
|
||||
|
||||
if ($bid > 0) {
|
||||
sync_backpack_items($conn, $new_list_id, $uid, $bid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$_SESSION['message'] = '<div class="alert alert-success" role="alert">Packliste erfolgreich erstellt!</div>';
|
||||
}
|
||||
|
||||
if ($household_id_for_user) {
|
||||
@@ -102,7 +138,6 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
log_household_action($conn, $household_id_for_user, $current_user_id, $log_message);
|
||||
}
|
||||
|
||||
$_SESSION['message'] = '<div class="alert alert-success" role="alert">Packliste erfolgreich erstellt!</div>';
|
||||
header("Location: manage_packing_list_items.php?id=" . $new_list_id);
|
||||
exit;
|
||||
} else {
|
||||
@@ -131,6 +166,21 @@ require_once 'header.php';
|
||||
<label for="name" class="form-label"><i class="fas fa-file-signature me-2 text-muted"></i>Name der Packliste</label>
|
||||
<input type="text" class="form-control" id="name" value="<?php echo htmlspecialchars($name); ?>" name="name" required>
|
||||
</div>
|
||||
|
||||
<!-- Template Selection -->
|
||||
<div class="mb-3">
|
||||
<label for="template_id" class="form-label"><i class="fas fa-copy me-2 text-muted"></i>Vorlage verwenden (Optional)</label>
|
||||
<select class="form-select" id="template_id" name="template_id">
|
||||
<option value="0" selected>Keine Vorlage (Leere Liste starten)</option>
|
||||
<?php foreach ($templates as $tpl): ?>
|
||||
<option value="<?php echo $tpl['id']; ?>">
|
||||
<?php echo htmlspecialchars($tpl['name']); ?>
|
||||
</option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<div class="form-text">Wenn du eine Vorlage wählst, werden Artikel, Träger und Rucksäcke daraus übernommen.</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="description" class="form-label"><i class="fas fa-align-left me-2 text-muted"></i>Beschreibung (optional)</label>
|
||||
<textarea class="form-control" id="description" name="description" rows="3"><?php echo htmlspecialchars($description); ?></textarea>
|
||||
@@ -144,43 +194,49 @@ require_once 'header.php';
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="col-md-5">
|
||||
<h5 class="mb-3"><i class="fas fa-users me-2 text-muted"></i>Teilnehmer & Rucksäcke</h5>
|
||||
<div class="card bg-light border-0">
|
||||
<div class="card-body">
|
||||
<p class="small text-muted">Wähle aus, wer mitkommt und wer welchen Rucksack trägt.</p>
|
||||
<div id="backpack-warning" class="alert alert-warning d-none small p-2 mb-2">
|
||||
<i class="fas fa-exclamation-triangle me-1"></i> Achtung: Ein Rucksack wurde mehrfach ausgewählt!
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$user_backpacks_json = [];
|
||||
$all_assigned_backpack_ids = [];
|
||||
|
||||
foreach ($available_users as $user):
|
||||
$user_backpacks = get_available_backpacks_for_user($conn, $user['id'], $household_id_for_user);
|
||||
$current_bp_id = 0;
|
||||
|
||||
$user_backpacks_json[$user['id']] = [
|
||||
'current_id' => $current_bp_id,
|
||||
'backpacks' => $user_backpacks
|
||||
];
|
||||
?>
|
||||
<div class="mb-3 pb-3 border-bottom">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input participation-check" type="checkbox" name="participate[<?php echo $user['id']; ?>]" value="1" checked id="part_<?php echo $user['id']; ?>">
|
||||
<label class="form-check-label fw-bold" for="part_<?php echo $user['id']; ?>">
|
||||
<?php echo htmlspecialchars($user['username']); ?>
|
||||
</label>
|
||||
</div>
|
||||
<div class="ms-4">
|
||||
<!-- Pass class for JS validation -->
|
||||
<?php echo render_backpack_card_selector($user, $current_bp_id, $user_backpacks); ?>
|
||||
</div>
|
||||
<div id="participants-section">
|
||||
<div class="card bg-light border-0">
|
||||
<div class="card-body">
|
||||
<p class="small text-muted">Wähle aus, wer mitkommt und wer welchen Rucksack trägt.</p>
|
||||
<div id="backpack-warning" class="alert alert-warning d-none small p-2 mb-2">
|
||||
<i class="fas fa-exclamation-triangle me-1"></i> Achtung: Ein Rucksack wurde mehrfach ausgewählt!
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php
|
||||
$user_backpacks_json = [];
|
||||
$all_assigned_backpack_ids = [];
|
||||
|
||||
foreach ($available_users as $user):
|
||||
$user_backpacks = get_available_backpacks_for_user($conn, $user['id'], $household_id_for_user);
|
||||
$current_bp_id = 0;
|
||||
|
||||
$user_backpacks_json[$user['id']] = [
|
||||
'current_id' => $current_bp_id,
|
||||
'backpacks' => $user_backpacks
|
||||
];
|
||||
?>
|
||||
<div class="mb-3 pb-3 border-bottom">
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input participation-check" type="checkbox" name="participate[<?php echo $user['id']; ?>]" value="1" checked id="part_<?php echo $user['id']; ?>">
|
||||
<label class="form-check-label fw-bold" for="part_<?php echo $user['id']; ?>">
|
||||
<?php echo htmlspecialchars($user['username']); ?>
|
||||
</label>
|
||||
</div>
|
||||
<div class="ms-4">
|
||||
<!-- Pass class for JS validation -->
|
||||
<?php echo render_backpack_card_selector($user, $current_bp_id, $user_backpacks); ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="template-notice" class="alert alert-info d-none">
|
||||
<i class="fas fa-info-circle me-2"></i> Da eine Vorlage gewählt wurde, werden die Träger und Rucksäcke aus der Vorlage übernommen. Du kannst diese später in der Listenansicht bearbeiten.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,25 +250,44 @@ require_once 'header.php';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Script for Validation -->
|
||||
<!-- Custom Script for Validation & Template Logic -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('createListForm');
|
||||
const submitBtn = document.getElementById('submitBtn');
|
||||
const warning = document.getElementById('backpack-warning');
|
||||
const templateSelect = document.getElementById('template_id');
|
||||
const participantsSection = document.getElementById('participants-section');
|
||||
const templateNotice = document.getElementById('template-notice');
|
||||
|
||||
// Toggle participants section based on template selection
|
||||
function toggleParticipants() {
|
||||
if (templateSelect.value !== "0") {
|
||||
participantsSection.classList.add('d-none');
|
||||
templateNotice.classList.remove('d-none');
|
||||
// Disable validation logic effectively by hiding warning
|
||||
warning.classList.add('d-none');
|
||||
submitBtn.disabled = false;
|
||||
} else {
|
||||
participantsSection.classList.remove('d-none');
|
||||
templateNotice.classList.add('d-none');
|
||||
validateBackpacks(); // Re-validate
|
||||
}
|
||||
}
|
||||
|
||||
templateSelect.addEventListener('change', toggleParticipants);
|
||||
|
||||
// Helper function to check duplicates
|
||||
function validateBackpacks() {
|
||||
if (templateSelect.value !== "0") return; // Skip if template is selected
|
||||
|
||||
const selected = [];
|
||||
let hasDuplicate = false;
|
||||
|
||||
// Find all hidden inputs that store the backpack ID (usually named backpacks[UserID])
|
||||
// Since render_backpack_card_selector uses a hidden input with name="backpacks[ID]", we select those
|
||||
// Find all hidden inputs that store the backpack ID
|
||||
const inputs = document.querySelectorAll('input[name^="backpacks["]');
|
||||
|
||||
inputs.forEach(input => {
|
||||
// Check if user participates
|
||||
// The input name is backpacks[123], we need to find checkbox participate[123]
|
||||
const userIdMatch = input.name.match(/backpacks\[(\d+)\]/);
|
||||
if (userIdMatch) {
|
||||
const userId = userIdMatch[1];
|
||||
@@ -244,15 +319,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
chk.addEventListener('change', validateBackpacks);
|
||||
});
|
||||
|
||||
// Listen for modal close/hide events (bootstrap specific) to re-validate
|
||||
// Since the backpack selection happens in a modal, validating on modal hide is efficient.
|
||||
// We use event delegation or attach to body since modals might be dynamically inserted/removed (though here they are static in footer usually)
|
||||
document.body.addEventListener('hidden.bs.modal', function (event) {
|
||||
validateBackpacks();
|
||||
});
|
||||
|
||||
// Initial check
|
||||
validateBackpacks();
|
||||
toggleParticipants();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -62,56 +62,10 @@ try {
|
||||
$new_list_id = $conn->insert_id;
|
||||
$stmt_new_list->close();
|
||||
|
||||
// KORREKTUR: Komplette Überarbeitung der Kopierlogik
|
||||
|
||||
// 4. Alle Artikel aus der originalen Liste mit ihrer ID holen
|
||||
$stmt_items = $conn->prepare("SELECT id, article_id, quantity, parent_packing_list_item_id, carrier_user_id FROM packing_list_items WHERE packing_list_id = ?");
|
||||
$stmt_items->bind_param("i", $original_list_id);
|
||||
$stmt_items->execute();
|
||||
$result_items = $stmt_items->get_result();
|
||||
$original_items = [];
|
||||
while ($row = $result_items->fetch_assoc()) {
|
||||
$original_items[] = $row;
|
||||
}
|
||||
$stmt_items->close();
|
||||
|
||||
if (!empty($original_items)) {
|
||||
// 5. Hierarchie-Klonen in zwei Schritten
|
||||
$old_to_new_id_map = []; // Speichert [alte_item_id] => neue_item_id
|
||||
|
||||
// Schritt 5a: Alle Items ohne Parent-Info kopieren und die ID-Zuordnung speichern
|
||||
$stmt_insert_item = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, article_id, quantity, carrier_user_id) VALUES (?, ?, ?, ?)");
|
||||
if (!$stmt_insert_item) throw new Exception("DB Prepare Fehler (Item Insert)");
|
||||
|
||||
foreach ($original_items as $item) {
|
||||
$stmt_insert_item->bind_param("iiii", $new_list_id, $item['article_id'], $item['quantity'], $item['carrier_user_id']);
|
||||
if (!$stmt_insert_item->execute()) throw new Exception("Fehler beim Kopieren von Artikel ID " . $item['article_id']);
|
||||
$new_item_id = $conn->insert_id;
|
||||
$old_to_new_id_map[$item['id']] = $new_item_id;
|
||||
}
|
||||
$stmt_insert_item->close();
|
||||
|
||||
// Schritt 5b: Die Parent-IDs für die neu erstellten Items aktualisieren
|
||||
$stmt_update_parent = $conn->prepare("UPDATE packing_list_items SET parent_packing_list_item_id = ? WHERE id = ?");
|
||||
if (!$stmt_update_parent) throw new Exception("DB Prepare Fehler (Parent Update)");
|
||||
|
||||
foreach ($original_items as $item) {
|
||||
// Wenn das Original-Item einen Parent hatte...
|
||||
if (!empty($item['parent_packing_list_item_id'])) {
|
||||
$old_parent_id = $item['parent_packing_list_item_id'];
|
||||
|
||||
// Stelle sicher, dass der Parent auch in unserer Map existiert
|
||||
if (isset($old_to_new_id_map[$old_parent_id])) {
|
||||
$new_parent_id = $old_to_new_id_map[$old_parent_id];
|
||||
$new_child_id = $old_to_new_id_map[$item['id']];
|
||||
|
||||
$stmt_update_parent->bind_param("ii", $new_parent_id, $new_child_id);
|
||||
if (!$stmt_update_parent->execute()) throw new Exception("Fehler beim Setzen der Hierarchie für Item " . $new_child_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
$stmt_update_parent->close();
|
||||
}
|
||||
// 4. Inhalt kopieren (Items, Träger, Hierarchien)
|
||||
// Nutzen der zentralen Hilfsfunktion, die auch beim Erstellen aus Templates verwendet wird.
|
||||
require_once 'packing_list_utils.php';
|
||||
copyPackingListContent($original_list_id, $new_list_id, $conn);
|
||||
|
||||
$conn->commit();
|
||||
$_SESSION['message'] = '<div class="alert alert-success" role="alert">Packliste erfolgreich zu "'.$copy_name.'" dupliziert!</div>';
|
||||
|
||||
@@ -29,7 +29,7 @@ if ($packing_list_id > 0) {
|
||||
$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 = $conn->prepare("SELECT id, name, description, user_id, household_id, is_template FROM packing_lists WHERE id = ?");
|
||||
$stmt_list_check->bind_param("i", $packing_list_id);
|
||||
$stmt_list_check->execute();
|
||||
$result = $stmt_list_check->get_result();
|
||||
@@ -178,12 +178,14 @@ function cleanup_old_backpack_containers($conn, $list_id, $user_id) {
|
||||
}
|
||||
}
|
||||
|
||||
$back_link = 'packing_lists.php' . (!empty($packing_list['is_template']) ? '?view=templates' : '');
|
||||
|
||||
?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0">Details: <?php echo htmlspecialchars($packing_list['name'] ?? ''); ?></h2>
|
||||
<a href="packing_lists.php" class="btn btn-sm btn-outline-light"><i class="fas fa-arrow-left me-2"></i>Zurück</a>
|
||||
<a href="<?php echo $back_link; ?>" class="btn btn-sm btn-outline-light"><i class="fas fa-arrow-left me-2"></i>Zurück</a>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<?php echo $message; ?>
|
||||
|
||||
101
src/packing_list_utils.php
Normal file
101
src/packing_list_utils.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// src/packing_list_utils.php - Hilfsfunktionen für Packlisten
|
||||
|
||||
/**
|
||||
* Kopiert den gesamten Inhalt einer Packliste (Items, Träger, Hierarchie) in eine neue Liste.
|
||||
*
|
||||
* @param int $sourceId ID der Quell-Packliste
|
||||
* @param int $targetId ID der Ziel-Packliste
|
||||
* @param mysqli $conn Datenbankverbindung
|
||||
* @throws Exception Bei Datenbankfehlern
|
||||
*/
|
||||
function copyPackingListContent($sourceId, $targetId, $conn) {
|
||||
// 1. Träger kopieren (packing_list_carriers)
|
||||
$stmt_carriers = $conn->prepare("SELECT user_id, backpack_id FROM packing_list_carriers WHERE packing_list_id = ?");
|
||||
if (!$stmt_carriers) throw new Exception("Prepare failed for carriers select: " . $conn->error);
|
||||
|
||||
$stmt_carriers->bind_param("i", $sourceId);
|
||||
$stmt_carriers->execute();
|
||||
$result_carriers = $stmt_carriers->get_result();
|
||||
|
||||
$stmt_insert_carrier = $conn->prepare("INSERT INTO packing_list_carriers (packing_list_id, user_id, backpack_id) VALUES (?, ?, ?)");
|
||||
if (!$stmt_insert_carrier) throw new Exception("Prepare failed for carriers insert: " . $conn->error);
|
||||
|
||||
while ($row = $result_carriers->fetch_assoc()) {
|
||||
$stmt_insert_carrier->bind_param("iii", $targetId, $row['user_id'], $row['backpack_id']);
|
||||
if (!$stmt_insert_carrier->execute()) throw new Exception("Error inserting carrier: " . $stmt_insert_carrier->error);
|
||||
}
|
||||
$stmt_carriers->close();
|
||||
$stmt_insert_carrier->close();
|
||||
|
||||
// 2. Items kopieren (packing_list_items)
|
||||
// Alle Items inklusive Rucksack/Fach-Info holen
|
||||
$sql_items = "SELECT id, article_id, quantity, parent_packing_list_item_id, carrier_user_id, backpack_id, backpack_compartment_id, name, order_index
|
||||
FROM packing_list_items WHERE packing_list_id = ?";
|
||||
$stmt_items = $conn->prepare($sql_items);
|
||||
if (!$stmt_items) throw new Exception("Prepare failed for items select: " . $conn->error);
|
||||
|
||||
$stmt_items->bind_param("i", $sourceId);
|
||||
$stmt_items->execute();
|
||||
$result_items = $stmt_items->get_result();
|
||||
|
||||
$original_items = [];
|
||||
while ($row = $result_items->fetch_assoc()) {
|
||||
$original_items[] = $row;
|
||||
}
|
||||
$stmt_items->close();
|
||||
|
||||
if (!empty($original_items)) {
|
||||
$old_to_new_id_map = []; // Mapping: alte_ID => neue_ID
|
||||
|
||||
// 2a. Items einfügen (zunächst ohne Parent-Referenz)
|
||||
$sql_insert = "INSERT INTO packing_list_items
|
||||
(packing_list_id, article_id, quantity, carrier_user_id, backpack_id, backpack_compartment_id, name, order_index)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
$stmt_insert_item = $conn->prepare($sql_insert);
|
||||
if (!$stmt_insert_item) throw new Exception("Prepare failed for item insert: " . $conn->error);
|
||||
|
||||
foreach ($original_items as $item) {
|
||||
$stmt_insert_item->bind_param(
|
||||
"iiiisiis",
|
||||
$targetId,
|
||||
$item['article_id'],
|
||||
$item['quantity'],
|
||||
$item['carrier_user_id'],
|
||||
$item['backpack_id'],
|
||||
$item['backpack_compartment_id'],
|
||||
$item['name'],
|
||||
$item['order_index']
|
||||
);
|
||||
|
||||
if (!$stmt_insert_item->execute()) {
|
||||
throw new Exception("Error copying item ID " . $item['article_id'] . ": " . $stmt_insert_item->error);
|
||||
}
|
||||
$old_to_new_id_map[$item['id']] = $conn->insert_id;
|
||||
}
|
||||
$stmt_insert_item->close();
|
||||
|
||||
// 2b. Parent-Referenzen aktualisieren (Hierarchie wiederherstellen)
|
||||
$stmt_update_parent = $conn->prepare("UPDATE packing_list_items SET parent_packing_list_item_id = ? WHERE id = ?");
|
||||
if (!$stmt_update_parent) throw new Exception("Prepare failed for parent update: " . $conn->error);
|
||||
|
||||
foreach ($original_items as $item) {
|
||||
if (!empty($item['parent_packing_list_item_id'])) {
|
||||
$old_parent_id = $item['parent_packing_list_item_id'];
|
||||
|
||||
// Prüfen, ob der Parent auch kopiert wurde (sollte immer so sein)
|
||||
if (isset($old_to_new_id_map[$old_parent_id])) {
|
||||
$new_parent_id = $old_to_new_id_map[$old_parent_id];
|
||||
$new_child_id = $old_to_new_id_map[$item['id']];
|
||||
|
||||
$stmt_update_parent->bind_param("ii", $new_parent_id, $new_child_id);
|
||||
if (!$stmt_update_parent->execute()) {
|
||||
throw new Exception("Error updating parent hierarchy: " . $stmt_update_parent->error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$stmt_update_parent->close();
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?php
|
||||
// packing_lists.php - Übersicht über alle Haushalts-Packlisten
|
||||
// KORREKTUR: Bearbeiten-Buttons werden nun auch für Haushaltsmitglieder bei geteilten Listen angezeigt.
|
||||
// packing_lists.php - Übersicht über alle Haushalts-Packlisten und Vorlagen
|
||||
|
||||
$page_title = "Packlisten des Haushalts";
|
||||
|
||||
@@ -16,8 +15,10 @@ require_once 'db_connect.php';
|
||||
require_once 'header.php';
|
||||
|
||||
$current_user_id = $_SESSION['user_id'];
|
||||
$message = '';
|
||||
$view = isset($_GET['view']) && $_GET['view'] === 'templates' ? 'templates' : 'lists';
|
||||
$is_template_view = ($view === 'templates');
|
||||
|
||||
$message = '';
|
||||
if (isset($_SESSION['message'])) {
|
||||
$message = $_SESSION['message'];
|
||||
unset($_SESSION['message']);
|
||||
@@ -30,25 +31,11 @@ $stmt_household->execute();
|
||||
$current_user_household_id = $stmt_household->get_result()->fetch_assoc()['household_id'];
|
||||
$stmt_household->close();
|
||||
|
||||
$household_member_ids = [$current_user_id];
|
||||
if ($current_user_household_id) {
|
||||
$stmt_members = $conn->prepare("SELECT id FROM users WHERE household_id = ?");
|
||||
$stmt_members->bind_param("i", $current_user_household_id);
|
||||
$stmt_members->execute();
|
||||
$result_members = $stmt_members->get_result();
|
||||
while ($row = $result_members->fetch_assoc()) {
|
||||
if (!in_array($row['id'], $household_member_ids)) {
|
||||
$household_member_ids[] = $row['id'];
|
||||
}
|
||||
}
|
||||
$stmt_members->close();
|
||||
}
|
||||
|
||||
$packing_lists = [];
|
||||
// KORREKTUR: Die SQL-Abfrage wurde vereinfacht und korrigiert, um alle relevanten Listen anzuzeigen.
|
||||
// Es werden alle Listen angezeigt, die entweder vom Nutzer erstellt wurden ODER mit seinem Haushalt geteilt sind.
|
||||
$is_template_val = $is_template_view ? 1 : 0;
|
||||
|
||||
$sql = "SELECT
|
||||
pl.id, pl.name, pl.description, pl.user_id, pl.household_id,
|
||||
pl.id, pl.name, pl.description, pl.user_id, pl.household_id, pl.is_template,
|
||||
u.username as creator_name,
|
||||
COUNT(DISTINCT COALESCE(pli.carrier_user_id, 'sonstiges')) AS carrier_count,
|
||||
SUM(pli.quantity * a.weight_grams) AS total_weight
|
||||
@@ -56,15 +43,17 @@ $sql = "SELECT
|
||||
JOIN users u ON pl.user_id = u.id
|
||||
LEFT JOIN packing_list_items pli ON pl.id = pli.packing_list_id
|
||||
LEFT JOIN articles a ON pli.article_id = a.id
|
||||
WHERE pl.user_id = ? OR pl.household_id = ?
|
||||
GROUP BY pl.id, pl.name, pl.description, pl.user_id, u.username, pl.household_id
|
||||
WHERE (pl.user_id = ? OR pl.household_id = ?) AND pl.is_template = ?
|
||||
GROUP BY pl.id, pl.name, pl.description, pl.user_id, u.username, pl.household_id, pl.is_template
|
||||
ORDER BY pl.name ASC";
|
||||
|
||||
$stmt = $conn->prepare($sql);
|
||||
if ($stmt === false) {
|
||||
$message .= '<div class="alert alert-danger" role="alert">SQL Prepare-Fehler: ' . $conn->error . '</div>';
|
||||
} else {
|
||||
$stmt->bind_param("ii", $current_user_id, $current_user_household_id);
|
||||
// Falls household_id NULL ist, nutzen wir 0 (da ID immer > 0)
|
||||
$h_id = $current_user_household_id ?: 0;
|
||||
$stmt->bind_param("iii", $current_user_id, $h_id, $is_template_val);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
while ($row = $result->fetch_assoc()) {
|
||||
@@ -76,28 +65,53 @@ $conn->close();
|
||||
?>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0">Packlisten im Haushalt</h2>
|
||||
<a href="add_packing_list.php" class="btn btn-sm btn-outline-light"><i class="fas fa-plus-circle me-2"></i>Neue Packliste erstellen</a>
|
||||
<div class="card-header">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h2 class="h4 mb-0"><?php echo $is_template_view ? 'Meine Vorlagen' : 'Packlisten im Haushalt'; ?></h2>
|
||||
<a href="add_packing_list.php" class="btn btn-sm btn-outline-light"><i class="fas fa-plus-circle me-2"></i>Neue Packliste erstellen</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs card-header-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?php echo !$is_template_view ? 'active' : ''; ?>" href="packing_lists.php?view=lists">
|
||||
<i class="fas fa-list me-2"></i>Packlisten
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link <?php echo $is_template_view ? 'active' : ''; ?>" href="packing_lists.php?view=templates">
|
||||
<i class="fas fa-copy me-2"></i>Vorlagen
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<?php if(!empty($message)) echo $message; ?>
|
||||
<?php if (empty($packing_lists)): ?>
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
Du hast noch keine Packlisten angelegt. <a href="add_packing_list.php" class="alert-link">Erstelle jetzt deine erste!</a>
|
||||
<?php if ($is_template_view): ?>
|
||||
Du hast noch keine Vorlagen. Erstelle eine Packliste und speichere sie als Vorlage.
|
||||
<?php else: ?>
|
||||
Du hast noch keine Packlisten angelegt. <a href="add_packing_list.php" class="alert-link">Erstelle jetzt deine erste!</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="row g-4">
|
||||
<?php foreach ($packing_lists as $list): ?>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card h-100 shadow-sm packing-list-card">
|
||||
<div class="card h-100 shadow-sm packing-list-card <?php echo $is_template_view ? 'border-info' : ''; ?>">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<img src="assets/images/rucksack_icon.png" alt="Packliste" class="card-icon">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<img src="assets/images/rucksack_icon.png" alt="Packliste" class="card-icon" style="opacity: <?php echo $is_template_view ? '0.7' : '1'; ?>;">
|
||||
<?php if ($is_template_view): ?>
|
||||
<span class="badge bg-info text-dark">VORLAGE</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<h5 class="card-title"><?php echo htmlspecialchars($list['name']); ?></h5>
|
||||
<p class="card-text text-muted flex-grow-1 small"><?php echo htmlspecialchars($list['description'] ?: 'Keine Beschreibung vorhanden.'); ?></p>
|
||||
<div class="mt-auto d-flex justify-content-between">
|
||||
<span class="badge bg-secondary creator-badge"><i class="fas fa-user-edit me-1"></i> Erstellt von <?php echo htmlspecialchars($list['creator_name']); ?></span>
|
||||
<span class="badge bg-secondary creator-badge"><i class="fas fa-user-edit me-1"></i> <?php echo htmlspecialchars($list['creator_name']); ?></span>
|
||||
<?php if (!empty($list['household_id'])): ?>
|
||||
<span class="badge bg-success" data-bs-toggle="tooltip" title="Für den Haushalt freigegeben"><i class="fas fa-users"></i></span>
|
||||
<?php else: ?>
|
||||
@@ -111,19 +125,50 @@ $conn->close();
|
||||
<span data-bs-toggle="tooltip" title="Gesamtgewicht"><i class="fas fa-weight-hanging text-muted"></i> <span class="badge bg-success rounded-pill ms-1"><?php echo number_format(($list['total_weight'] ?? 0) / 1000, 2, ',', '.'); ?> kg</span></span>
|
||||
</div>
|
||||
<div>
|
||||
<a href="packing_list_detail.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-success" title="Details ansehen"><i class="fas fa-eye"></i></a>
|
||||
<?php
|
||||
$is_owner = ($list['user_id'] == $current_user_id);
|
||||
$can_edit_household_list = (!empty($list['household_id']) && $list['household_id'] == $current_user_household_id);
|
||||
|
||||
// Actions für Vorlagen und Listen unterscheiden
|
||||
if ($is_template_view) {
|
||||
// VORLAGEN ACTIONS
|
||||
?>
|
||||
<form action="add_packing_list.php" method="POST" class="d-inline">
|
||||
<!-- Hack: Wir nutzen das Formular von add_packing_list, aber wir müssen es eigentlich via GET -> UI aufrufen oder POST direkt?
|
||||
add_packing_list erwartet POST mit 'name' etc.
|
||||
Besser: Link zu add_packing_list.php mit Preset. Aber das haben wir nicht gebaut.
|
||||
Daher: Nur Edit/Delete hier. Das "Nutzen" passiert im "Erstellen" Dialog.
|
||||
-->
|
||||
</form>
|
||||
|
||||
<!-- Bearbeiten (Inhalt ändern) -->
|
||||
<?php if ($is_owner || $can_edit_household_list): ?>
|
||||
<a href="manage_packing_list_items.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-primary" title="Vorlage bearbeiten"><i class="fas fa-pencil-alt"></i></a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
} else {
|
||||
// LIST ACTIONS (Standard)
|
||||
?>
|
||||
<a href="packing_list_detail.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-success" title="Details ansehen"><i class="fas fa-eye"></i></a>
|
||||
<?php if ($is_owner || $can_edit_household_list): ?>
|
||||
<a href="manage_packing_list_items.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Artikel verwalten"><i class="fas fa-boxes"></i></a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_owner): ?>
|
||||
<!-- Save As Template -->
|
||||
<a href="save_as_template.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-warning" title="Als Vorlage speichern"><i class="fas fa-save"></i></a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<?php if ($is_owner || $can_edit_household_list): ?>
|
||||
<a href="edit_packing_list_details.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-primary" title="Details bearbeiten"><i class="fas fa-pencil-alt"></i></a>
|
||||
<a href="manage_packing_list_items.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-secondary" title="Artikel verwalten"><i class="fas fa-boxes"></i></a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($is_owner): ?>
|
||||
<a href="share_packing_list.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-info" title="Teilen"><i class="fas fa-share-alt"></i></a>
|
||||
<a href="duplicate_packing_list.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-dark" title="Duplizieren" onclick="return confirm('Möchten Sie diese Packliste wirklich duplizieren?');"><i class="fas fa-copy"></i></a>
|
||||
<a href="delete_packing_list.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-danger ms-2" title="Löschen" onclick="return confirm('Sind Sie sicher, dass Sie diese Packliste löschen möchten?');"><i class="fas fa-trash"></i></a>
|
||||
<?php if (!$is_template_view): ?>
|
||||
<a href="duplicate_packing_list.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-dark" title="Duplizieren" onclick="return confirm('Möchten Sie diese Packliste wirklich duplizieren?');"><i class="fas fa-copy"></i></a>
|
||||
<?php endif; ?>
|
||||
<a href="delete_packing_list.php?id=<?php echo $list['id']; ?>" class="btn btn-sm btn-outline-danger ms-2" title="Löschen" onclick="return confirm('Sind Sie sicher, dass Sie diese <?php echo $is_template_view ? 'Vorlage' : 'Packliste'; ?> löschen möchten?');"><i class="fas fa-trash"></i></a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,4 +180,4 @@ $conn->close();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
<?php require_once 'footer.php'; ?>
|
||||
58
src/save_as_template.php
Normal file
58
src/save_as_template.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
// src/save_as_template.php - Speichert eine Packliste als Vorlage (Kopie)
|
||||
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
if (!isset($_SESSION['user_id'])) {
|
||||
header("Location: login.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once 'db_connect.php';
|
||||
require_once 'packing_list_utils.php';
|
||||
|
||||
$current_user_id = $_SESSION['user_id'];
|
||||
$list_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
|
||||
|
||||
if ($list_id <= 0) {
|
||||
header("Location: packing_lists.php");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prüfen, ob Liste existiert und User Zugriff hat
|
||||
$stmt = $conn->prepare("SELECT name, description, household_id FROM packing_lists WHERE id = ? AND (user_id = ? OR household_id = (SELECT household_id FROM users WHERE id = ?))");
|
||||
// Eigentlich darf nur Owner oder Household-Admin kopieren? Lassen wir es für alle mit Lesezugriff zu.
|
||||
$stmt->bind_param("iii", $list_id, $current_user_id, $current_user_id);
|
||||
$stmt->execute();
|
||||
$result = $stmt->get_result();
|
||||
if ($result->num_rows === 0) {
|
||||
$_SESSION['message'] = '<div class="alert alert-danger">Packliste nicht gefunden oder keine Berechtigung.</div>';
|
||||
header("Location: packing_lists.php");
|
||||
exit;
|
||||
}
|
||||
$list = $result->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
$new_name = "Vorlage: " . $list['name'];
|
||||
|
||||
// Neue Liste (Template) anlegen
|
||||
$stmt_insert = $conn->prepare("INSERT INTO packing_lists (user_id, household_id, name, description, is_template) VALUES (?, ?, ?, ?, 1)");
|
||||
$stmt_insert->bind_param("isss", $current_user_id, $list['household_id'], $new_name, $list['description']);
|
||||
|
||||
if ($stmt_insert->execute()) {
|
||||
$new_template_id = $conn->insert_id;
|
||||
try {
|
||||
copyPackingListContent($list_id, $new_template_id, $conn);
|
||||
$_SESSION['message'] = '<div class="alert alert-success">Packliste erfolgreich als Vorlage gespeichert! Du findest sie im Tab "Vorlagen".</div>';
|
||||
} catch (Exception $e) {
|
||||
// Aufräumen bei Fehler wäre gut, aber hier einfach Fehler melden
|
||||
$_SESSION['message'] = '<div class="alert alert-warning">Vorlage erstellt, aber Inhalt konnte nicht vollständig kopiert werden: ' . $e->getMessage() . '</div>';
|
||||
}
|
||||
} else {
|
||||
$_SESSION['message'] = '<div class="alert alert-danger">Fehler beim Erstellen der Vorlage: ' . $conn->error . '</div>';
|
||||
}
|
||||
|
||||
header("Location: packing_lists.php?view=templates");
|
||||
exit;
|
||||
?>
|
||||
Reference in New Issue
Block a user