Feature: Verbesserungen an Artikel-Bildern, Rucksäcken und Filtern
- add_article.php: Auswahl bereits hochgeladener Bilder hinzugefügt. - edit_backpack.php: Link zur Herstellerseite (neue Spalte product_url) und Fix für 'Neuer Hersteller' hinzugefügt. Auto-Migration für DB-Spalte integriert. - manage_packing_list_items.php: Filteroptionen um 'Alle' erweitert.
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
// add_article.php - Formular zum Hinzufügen eines neuen Artikels
|
||||
// FINALE, VOLLSTÄNDIGE VERSION mit allen Funktionen (Komponenten, Bestand, etc.)
|
||||
// KORREKTUR: SSL-Verifizierung und @-Fehlerunterdrückung korrigiert.
|
||||
// FINALE, VOLLSTÄNDIGE VERSION mit Bilder-Auswahl
|
||||
|
||||
$page_title = "Neuen Artikel hinzufügen";
|
||||
|
||||
@@ -45,25 +44,28 @@ if ($household_id_for_user) {
|
||||
$placeholders = implode(',', array_fill(0, count($household_member_ids), '?'));
|
||||
$types = str_repeat('i', count($household_member_ids));
|
||||
|
||||
// Lade Kategorien, Hersteller, Lagerorte und potenzielle Eltern-Artikel
|
||||
// Lade Kategorien
|
||||
$stmt_cat_load = $conn->prepare("SELECT id, name FROM categories WHERE user_id IN ($placeholders) ORDER BY name ASC");
|
||||
$stmt_cat_load->bind_param($types, ...$household_member_ids);
|
||||
$stmt_cat_load->execute();
|
||||
$categories = $stmt_cat_load->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_cat_load->close();
|
||||
|
||||
// Lade Hersteller
|
||||
$stmt_man_load = $conn->prepare("SELECT id, name FROM manufacturers WHERE user_id IN ($placeholders) ORDER BY name ASC");
|
||||
$stmt_man_load->bind_param($types, ...$household_member_ids);
|
||||
$stmt_man_load->execute();
|
||||
$manufacturers = $stmt_man_load->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_man_load->close();
|
||||
|
||||
// Lade Lagerorte
|
||||
$stmt_loc_load = $conn->prepare("SELECT id, name, parent_id FROM storage_locations WHERE user_id IN ($placeholders) ORDER BY parent_id, name");
|
||||
$stmt_loc_load->bind_param($types, ...$household_member_ids);
|
||||
$stmt_loc_load->execute();
|
||||
$all_locations = $stmt_loc_load->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_loc_load->close();
|
||||
|
||||
// Lade Eltern-Artikel
|
||||
$all_parent_params = array_merge($household_member_ids, [$household_id_for_user]);
|
||||
$all_parent_types = $types . 'i';
|
||||
$stmt_parent_articles = $conn->prepare("SELECT a.id, a.name, m.name as manufacturer_name, a.product_designation FROM articles a LEFT JOIN manufacturers m ON a.manufacturer_id = m.id WHERE (a.user_id IN ($placeholders) OR a.household_id = ?) AND a.parent_article_id IS NULL ORDER BY a.name ASC");
|
||||
@@ -72,6 +74,14 @@ $stmt_parent_articles->execute();
|
||||
$parent_articles = $stmt_parent_articles->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_parent_articles->close();
|
||||
|
||||
// Lade letzte Bilder (für Auswahl)
|
||||
$stmt_imgs = $conn->prepare("SELECT DISTINCT image_url FROM articles WHERE user_id IN ($placeholders) AND image_url IS NOT NULL AND image_url != '' AND image_url != '0' ORDER BY created_at DESC LIMIT 24");
|
||||
$stmt_imgs->bind_param($types, ...$household_member_ids);
|
||||
$stmt_imgs->execute();
|
||||
$recent_images = $stmt_imgs->get_result()->fetch_all(MYSQLI_ASSOC);
|
||||
$stmt_imgs->close();
|
||||
|
||||
// Strukturierung Lagerorte
|
||||
$storage_locations_structured = [];
|
||||
foreach ($all_locations as $loc) { if ($loc['parent_id'] === NULL) { if (!isset($storage_locations_structured[$loc['id']])) { $storage_locations_structured[$loc['id']] = ['name' => $loc['name'], 'children' => []]; } } }
|
||||
foreach ($all_locations as $loc) { if ($loc['parent_id'] !== NULL && isset($storage_locations_structured[$loc['parent_id']])) { $storage_locations_structured[$loc['parent_id']]['children'][] = $loc; } }
|
||||
@@ -80,50 +90,31 @@ $upload_dir = 'uploads/images/';
|
||||
if (!is_dir($upload_dir)) { @mkdir($upload_dir, 0777, true); }
|
||||
|
||||
function save_image_from_url($url, $upload_dir) {
|
||||
// KORREKTUR: Unsichere SSL-Optionen entfernt, @-Operator entfernt
|
||||
$context_options = ["http" => ["header" => "User-Agent: Mozilla/5.0\r\n", "timeout" => 10]];
|
||||
$context = stream_context_create($context_options);
|
||||
|
||||
// Fehlerbehandlung für file_get_contents
|
||||
$image_data = file_get_contents($url, false, $context);
|
||||
if ($image_data === false) {
|
||||
return [false, "Bild konnte von der URL nicht heruntergeladen werden. Überprüfen Sie den Link und die Erreichbarkeit."];
|
||||
}
|
||||
|
||||
// Fehlerbehandlung für getimagesizefromstring
|
||||
$image_info = getimagesizefromstring($image_data);
|
||||
if ($image_info === false) {
|
||||
return [false, "Die angegebene URL führt nicht zu einem gültigen Bild."];
|
||||
}
|
||||
|
||||
$allowed_mime_types = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!in_array($image_info['mime'], $allowed_mime_types)) {
|
||||
return [false, "Nicht unterstützter Bildtyp: " . htmlspecialchars($image_info['mime'])];
|
||||
}
|
||||
|
||||
$extension = image_type_to_extension($image_info[2], false);
|
||||
$unique_file_name = uniqid('img_url_', true) . '.' . $extension;
|
||||
$destination = $upload_dir . $unique_file_name;
|
||||
|
||||
if (file_put_contents($destination, $image_data)) {
|
||||
return [true, $destination];
|
||||
}
|
||||
|
||||
return [false, "Bild konnte nicht auf dem Server gespeichert werden."];
|
||||
$image_data = @file_get_contents($url, false, $context);
|
||||
if ($image_data === false) return [false, "Bild-Download fehlgeschlagen."];
|
||||
$image_info = @getimagesizefromstring($image_data);
|
||||
if ($image_info === false) return [false, "Ungültiges Bild."];
|
||||
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
|
||||
if (!in_array($image_info['mime'], $allowed)) return [false, "Format nicht unterstützt."];
|
||||
$ext = image_type_to_extension($image_info[2], false);
|
||||
$name = uniqid('img_url_', true) . '.' . $ext;
|
||||
if (file_put_contents($upload_dir . $name, $image_data)) return [true, $upload_dir . $name];
|
||||
return [false, "Speicherfehler."];
|
||||
}
|
||||
|
||||
function save_image_from_base64($base64_string, $upload_dir) {
|
||||
if (preg_match('/^data:image\/(\w+);base64,/', $base64_string, $type)) {
|
||||
$data = substr($base64_string, strpos($base64_string, ',') + 1);
|
||||
$type = strtolower($type[1]);
|
||||
if (!in_array($type, ['jpg', 'jpeg', 'png', 'gif'])) { return [false, "Nicht unterstützter Bildtyp aus der Zwischenablage."]; }
|
||||
if (!in_array($type, ['jpg', 'jpeg', 'png', 'gif'])) return [false, "Format nicht unterstützt."];
|
||||
$data = base64_decode($data);
|
||||
if ($data === false) { return [false, "Base64-Dekodierung fehlgeschlagen."]; }
|
||||
} else { return [false, "Ungültiger Base64-String."]; }
|
||||
$unique_file_name = uniqid('img_paste_', true) . '.' . $type;
|
||||
$destination = $upload_dir . $unique_file_name;
|
||||
if (file_put_contents($destination, $data)) { return [true, $destination]; }
|
||||
return [false, "Bild aus der Zwischenablage konnte nicht gespeichert werden."];
|
||||
if ($data === false) return [false, "Dekodierfehler."];
|
||||
$name = uniqid('img_paste_', true) . '.' . $type;
|
||||
if (file_put_contents($upload_dir . $name, $data)) return [true, $upload_dir . $name];
|
||||
}
|
||||
return [false, "Ungültiges Base64."];
|
||||
}
|
||||
|
||||
if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
@@ -138,6 +129,7 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$product_url = trim($_POST['product_url']);
|
||||
$product_designation = trim($_POST['product_designation']);
|
||||
$pasted_image_data = $_POST['pasted_image_data'] ?? '';
|
||||
$selected_existing_image = trim($_POST['selected_existing_image'] ?? '');
|
||||
|
||||
if (isset($_POST['manufacturer_id']) && $_POST['manufacturer_id'] === 'new') {
|
||||
$new_manufacturer_name = trim($_POST['new_manufacturer_name']);
|
||||
@@ -173,6 +165,10 @@ if ($_SERVER["REQUEST_METHOD"] == "POST") {
|
||||
$image_url_for_db = $destination;
|
||||
} else { $image_error = "Fehler beim Verschieben der hochgeladenen Datei."; }
|
||||
}
|
||||
elseif (!empty($selected_existing_image)) {
|
||||
// User chose an existing image
|
||||
$image_url_for_db = $selected_existing_image;
|
||||
}
|
||||
elseif (!empty($image_url_from_input)) {
|
||||
list($success, $result) = save_image_from_url($image_url_from_input, $upload_dir);
|
||||
if ($success) { $image_url_for_db = $result; } else { $image_error = $result; }
|
||||
@@ -213,10 +209,6 @@ $conn->close();
|
||||
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
|
||||
|
||||
<!-- Tom Select CSS/JS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/css/tom-select.bootstrap5.min.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js"></script>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h2 class="h4 mb-0"><i class="fas fa-plus-circle me-2"></i>Neuen Artikel erstellen</h2>
|
||||
@@ -226,6 +218,7 @@ $conn->close();
|
||||
<?php if(!empty($message)) echo $message; ?>
|
||||
<form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post" enctype="multipart/form-data">
|
||||
<input type="hidden" name="pasted_image_data" id="pasted_image_data">
|
||||
<input type="hidden" name="selected_existing_image" id="selected_existing_image">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7 d-flex flex-column">
|
||||
<div class="card section-card mb-4">
|
||||
@@ -272,6 +265,23 @@ $conn->close();
|
||||
<div class="card-header"><h5 class="mb-0"><i class="fas fa-image me-2"></i>Bild & Produktseite</h5></div>
|
||||
<div class="card-body d-flex flex-column">
|
||||
<div class="mb-3"><label for="product_url" class="form-label">Produktseite (URL)</label><input type="url" class="form-control" id="product_url" name="product_url" value="<?php echo htmlspecialchars($product_url); ?>" placeholder="https://..."></div>
|
||||
|
||||
<!-- New: Existing Images Selection -->
|
||||
<?php if(!empty($recent_images)): ?>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Aus vorhandenen Bildern wählen:</label>
|
||||
<div class="d-flex flex-wrap gap-2 p-2 border rounded bg-light" style="max-height: 150px; overflow-y: auto;">
|
||||
<?php foreach($recent_images as $img): ?>
|
||||
<img src="<?php echo htmlspecialchars($img['image_url']); ?>"
|
||||
class="existing-image-option rounded border"
|
||||
style="width: 50px; height: 50px; object-fit: cover; cursor: pointer;"
|
||||
data-url="<?php echo htmlspecialchars($img['image_url']); ?>"
|
||||
title="Klicken zum Auswählen">
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mb-3"><label for="image_file" class="form-label">1. Bild hochladen</label><input class="form-control" type="file" id="image_file" name="image_file" accept="image/*"></div>
|
||||
<div class="mb-3"><label for="image_url" class="form-label">2. ODER Bild-URL angeben</label><input type="url" class="form-control" id="image_url" name="image_url" placeholder="https://..."></div>
|
||||
<div class="mb-3"><label class="form-label">3. ODER Bild einfügen (Strg+V)</label><div id="pasteArea" class="paste-area"><i class="fas fa-paste"></i> Hier klicken & einfügen</div></div>
|
||||
@@ -293,28 +303,44 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const select = document.getElementById(selectId);
|
||||
const container = document.getElementById(containerId);
|
||||
if (!select || !container) return;
|
||||
const input = container.querySelector('input');
|
||||
|
||||
function toggle() {
|
||||
// Must check TomSelect wrapper input if handled, but the value propagates
|
||||
const isNew = select.value === 'new';
|
||||
container.style.display = isNew ? 'block' : 'none';
|
||||
const input = container.querySelector('input');
|
||||
if (input) input.required = isNew;
|
||||
if (!isNew && input) { input.value = ''; }
|
||||
}
|
||||
// Native change (for backup)
|
||||
select.addEventListener('change', toggle);
|
||||
// Initial
|
||||
toggle();
|
||||
|
||||
// Return the toggle function so TomSelect can call it
|
||||
return toggle;
|
||||
}
|
||||
setupAddNewOption('manufacturer_id', 'new_manufacturer_container');
|
||||
setupAddNewOption('category_id', 'new_category_container');
|
||||
const toggleMan = setupAddNewOption('manufacturer_id', 'new_manufacturer_container');
|
||||
const toggleCat = setupAddNewOption('category_id', 'new_category_container');
|
||||
|
||||
const imageFileInput = document.getElementById('image_file');
|
||||
const imageUrlInput = document.getElementById('image_url');
|
||||
const imagePreview = document.getElementById('imagePreview');
|
||||
const pasteArea = document.getElementById('pasteArea');
|
||||
const pastedImageDataInput = document.getElementById('pasted_image_data');
|
||||
const selectedExistingInput = document.getElementById('selected_existing_image');
|
||||
const existingImages = document.querySelectorAll('.existing-image-option');
|
||||
|
||||
function clearOtherImageInputs(source) {
|
||||
if (source !== 'file') imageFileInput.value = '';
|
||||
if (source !== 'url') imageUrlInput.value = '';
|
||||
if (source !== 'paste') pastedImageDataInput.value = '';
|
||||
if (source !== 'existing') selectedExistingInput.value = '';
|
||||
|
||||
// Reset styles
|
||||
existingImages.forEach(img => img.classList.remove('border-primary', 'border-3'));
|
||||
}
|
||||
|
||||
if(imageFileInput) {
|
||||
imageFileInput.addEventListener('change', function() {
|
||||
if (this.files && this.files[0]) {
|
||||
@@ -325,6 +351,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Existing Image Click Handler
|
||||
existingImages.forEach(img => {
|
||||
img.addEventListener('click', function() {
|
||||
const url = this.getAttribute('data-url');
|
||||
imagePreview.src = url;
|
||||
clearOtherImageInputs('existing');
|
||||
selectedExistingInput.value = url;
|
||||
this.classList.add('border-primary', 'border-3');
|
||||
});
|
||||
});
|
||||
|
||||
if (pasteArea) {
|
||||
pasteArea.addEventListener('paste', function(e) {
|
||||
e.preventDefault();
|
||||
@@ -345,7 +383,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
pasteArea.addEventListener('dragover', (e) => { e.preventDefault(); pasteArea.classList.add('hover'); });
|
||||
pasteArea.addEventListener('dragleave', () => pasteArea.classList.remove('hover'));
|
||||
pasteArea.addEventListener('drop', (e) => { e.preventDefault(); pasteArea.classList.remove('hover'); });
|
||||
// Click to focus
|
||||
pasteArea.setAttribute('tabindex', '0');
|
||||
pasteArea.addEventListener('click', () => pasteArea.focus());
|
||||
}
|
||||
|
||||
const consumableCheckbox = document.getElementById('consumable');
|
||||
const quantityOwnedInput = document.getElementById('quantity_owned');
|
||||
consumableCheckbox.addEventListener('change', function() {
|
||||
@@ -358,20 +400,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
// Initialize Tom Select for searchable dropdowns
|
||||
const tsOptions = {
|
||||
const tsOptionsBase = {
|
||||
create: false,
|
||||
sortField: { field: "text", direction: "asc" },
|
||||
onChange: function(value) {
|
||||
// Force trigger change event for the "New" option toggle logic
|
||||
const event = new Event('change');
|
||||
this.input.dispatchEvent(event);
|
||||
}
|
||||
sortField: { field: "text", direction: "asc" }
|
||||
};
|
||||
|
||||
if(document.getElementById('manufacturer_id')) new TomSelect('#manufacturer_id', tsOptions);
|
||||
if(document.getElementById('category_id')) new TomSelect('#category_id', tsOptions);
|
||||
if(document.getElementById('parent_article_id')) new TomSelect('#parent_article_id', tsOptions);
|
||||
if(document.getElementById('storage_location_id')) new TomSelect('#storage_location_id', tsOptions);
|
||||
if(document.getElementById('manufacturer_id')) {
|
||||
new TomSelect('#manufacturer_id', {
|
||||
...tsOptionsBase,
|
||||
onChange: function(value) { if(toggleMan) toggleMan(); }
|
||||
});
|
||||
}
|
||||
if(document.getElementById('category_id')) {
|
||||
new TomSelect('#category_id', {
|
||||
...tsOptionsBase,
|
||||
onChange: function(value) { if(toggleCat) toggleCat(); }
|
||||
});
|
||||
}
|
||||
if(document.getElementById('parent_article_id')) new TomSelect('#parent_article_id', tsOptionsBase);
|
||||
if(document.getElementById('storage_location_id')) new TomSelect('#storage_location_id', tsOptionsBase);
|
||||
});
|
||||
</script>
|
||||
<?php require_once 'footer.php'; ?>
|
||||
<?php require_once 'footer.php'; ?>
|
||||
@@ -23,6 +23,13 @@ $backpack = null;
|
||||
$compartments = [];
|
||||
$message = '';
|
||||
$image_url = '';
|
||||
$product_url = '';
|
||||
|
||||
// AUTO-MIGRATION: Check if product_url column exists, if not add it
|
||||
$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 Household
|
||||
$household_id = null;
|
||||
@@ -75,6 +82,7 @@ if ($backpack_id > 0) {
|
||||
if ($result->num_rows > 0) {
|
||||
$backpack = $result->fetch_assoc();
|
||||
$image_url = $backpack['image_url'];
|
||||
$product_url = $backpack['product_url'] ?? '';
|
||||
|
||||
// Load Compartments
|
||||
$stmt_c = $conn->prepare("SELECT * FROM backpack_compartments WHERE backpack_id = ? ORDER BY sort_order ASC");
|
||||
@@ -107,11 +115,21 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
if ($_POST['manufacturer_select'] === 'new') {
|
||||
$new_man = trim($_POST['new_manufacturer_name']);
|
||||
if (!empty($new_man)) {
|
||||
// Optional: Save to manufacturers table for future use
|
||||
$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();
|
||||
$manufacturer = $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();
|
||||
$res_check = $stmt_check_man->get_result();
|
||||
|
||||
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();
|
||||
$manufacturer = $new_man;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Look up name from ID
|
||||
@@ -129,6 +147,7 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
$weight = intval($_POST['weight_grams']);
|
||||
$volume = intval($_POST['volume_liters']);
|
||||
$share_household = isset($_POST['share_household']) ? 1 : 0;
|
||||
$product_url_input = trim($_POST['product_url'] ?? '');
|
||||
|
||||
// Image Handling
|
||||
$image_url_for_db = $image_url; // Keep existing by default
|
||||
@@ -183,13 +202,13 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
|
||||
if ($backpack_id > 0) {
|
||||
// Update
|
||||
$stmt = $conn->prepare("UPDATE backpacks SET name=?, manufacturer=?, model=?, weight_grams=?, volume_liters=?, household_id=?, image_url=? WHERE id=? AND user_id=?");
|
||||
$stmt->bind_param("sssiissii", $name, $manufacturer, $model, $weight, $volume, $final_household_id, $image_url_for_db, $backpack_id, $user_id);
|
||||
$stmt = $conn->prepare("UPDATE backpacks SET name=?, manufacturer=?, model=?, weight_grams=?, volume_liters=?, household_id=?, image_url=?, product_url=? WHERE id=? AND user_id=?");
|
||||
$stmt->bind_param("sssiisssii", $name, $manufacturer, $model, $weight, $volume, $final_household_id, $image_url_for_db, $product_url_input, $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, image_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("iisssiis", $user_id, $final_household_id, $name, $manufacturer, $model, $weight, $volume, $image_url_for_db);
|
||||
$stmt = $conn->prepare("INSERT INTO backpacks (user_id, household_id, name, manufacturer, model, weight_grams, volume_liters, image_url, product_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
|
||||
$stmt->bind_param("iisssiiss", $user_id, $final_household_id, $name, $manufacturer, $model, $weight, $volume, $image_url_for_db, $product_url_input);
|
||||
$stmt->execute();
|
||||
$backpack_id = $stmt->insert_id;
|
||||
}
|
||||
@@ -243,9 +262,6 @@ if ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle Form Submission BEFORE loading header
|
||||
// ... (existing logic) ...
|
||||
|
||||
require_once 'header.php';
|
||||
?>
|
||||
|
||||
@@ -302,6 +318,13 @@ require_once 'header.php';
|
||||
<label class="form-label">Volumen (Liter)</label>
|
||||
<input type="number" name="volume_liters" class="form-control" value="<?php echo htmlspecialchars($backpack['volume_liters'] ?? 0); ?>">
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label">Hersteller-Link (Produktseite)</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fas fa-link"></i></span>
|
||||
<input type="url" name="product_url" class="form-control" value="<?php echo htmlspecialchars($product_url); ?>" placeholder="https://...">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="share_household" name="share_household" <?php echo (!empty($backpack['household_id']) || $backpack_id == 0) ? 'checked' : ''; ?>>
|
||||
@@ -378,14 +401,35 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const manSelect = document.getElementById('manufacturer_select');
|
||||
const manContainer = document.getElementById('new_manufacturer_container');
|
||||
|
||||
function toggleManContainer(val) {
|
||||
if (val === 'new') {
|
||||
manContainer.style.display = 'block';
|
||||
const input = manContainer.querySelector('input');
|
||||
if(input) input.required = true;
|
||||
} else {
|
||||
manContainer.style.display = 'none';
|
||||
const input = manContainer.querySelector('input');
|
||||
if(input) input.required = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (manSelect) {
|
||||
// Initial check
|
||||
toggleManContainer(manSelect.value);
|
||||
|
||||
// Vanilla Listener (Backup)
|
||||
manSelect.addEventListener('change', function() {
|
||||
if (this.value === 'new') {
|
||||
manContainer.style.display = 'block';
|
||||
manContainer.querySelector('input').required = true;
|
||||
} else {
|
||||
manContainer.style.display = 'none';
|
||||
manContainer.querySelector('input').required = false;
|
||||
toggleManContainer(this.value);
|
||||
});
|
||||
}
|
||||
|
||||
// Tom Select Init
|
||||
if(manSelect) {
|
||||
new TomSelect(manSelect, {
|
||||
create: false,
|
||||
sortField: { field: "text", direction: "asc" },
|
||||
onChange: function(value) {
|
||||
toggleManContainer(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -401,8 +445,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
document.getElementById('add-compartment').addEventListener('click', function() {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'input-group mb-2 compartment-row';
|
||||
div.innerHTML = `
|
||||
<span class="input-group-text"><i class="fas fa-grip-lines text-muted"></i></span>
|
||||
div.innerHTML =
|
||||
`<span class="input-group-text"><i class="fas fa-grip-lines text-muted"></i></span>
|
||||
<input type="text" name="compartment_names[]" class="form-control" placeholder="Fachname">
|
||||
<input type="hidden" name="compartment_ids[]" value="0">
|
||||
<button type="button" class="btn btn-outline-danger btn-remove-comp"><i class="fas fa-times"></i></button>
|
||||
@@ -416,7 +460,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Image Handling Logic (copied from add_article.php pattern)
|
||||
// Image Handling Logic
|
||||
const imageFileInput = document.getElementById('image_file');
|
||||
const imagePreview = document.getElementById('imagePreview');
|
||||
const pasteArea = document.getElementById('pasteArea');
|
||||
@@ -433,7 +477,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
|
||||
if (pasteArea) {
|
||||
// ... existing paste logic ...
|
||||
pasteArea.addEventListener('paste', function(e) {
|
||||
e.preventDefault();
|
||||
const items = (e.clipboardData || window.clipboardData).items;
|
||||
@@ -451,24 +494,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
// Add focus to paste area on click so it can receive paste events
|
||||
pasteArea.setAttribute('tabindex', '0');
|
||||
pasteArea.addEventListener('click', () => pasteArea.focus());
|
||||
}
|
||||
|
||||
// Tom Select Init
|
||||
const manSelect = document.getElementById('manufacturer_select');
|
||||
if(manSelect) {
|
||||
new TomSelect(manSelect, {
|
||||
create: false,
|
||||
sortField: { field: "text", direction: "asc" },
|
||||
onChange: function(value) {
|
||||
const event = new Event('change');
|
||||
manSelect.dispatchEvent(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php require_once 'footer.php'; ?>
|
||||
<?php require_once 'footer.php'; ?>
|
||||
|
||||
@@ -118,8 +118,8 @@ $conn->close();
|
||||
<h5 class="mb-0">Verfügbare Artikel</h5>
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-12"><input type="text" id="filter-text" class="form-control form-control-sm" placeholder="Artikel, Hersteller, Modell suchen..."></div>
|
||||
<div class="col-md-6"><select id="filter-category" class="form-select form-select-sm"><option value="">Alle Kategorien</option><?php foreach($categories as $c) echo '<option>'.htmlspecialchars($c).'</option>'; ?></select></div>
|
||||
<div class="col-md-6"><select id="filter-manufacturer" class="form-select form-select-sm"><option value="">Alle Hersteller</option><?php foreach($manufacturers as $m) echo '<option>'.htmlspecialchars($m).'</option>'; ?></select></div>
|
||||
<div class="col-md-6"><select id="filter-category" class="form-select form-select-sm"><option value="">-- Alle Kategorien --</option><?php foreach($categories as $c) echo '<option>'.htmlspecialchars($c).'</option>'; ?></select></div>
|
||||
<div class="col-md-6"><select id="filter-manufacturer" class="form-select form-select-sm"><option value="">-- Alle Hersteller --</option><?php foreach($manufacturers as $m) echo '<option>'.htmlspecialchars($m).'</option>'; ?></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pane-content" id="available-items-list"></div>
|
||||
@@ -220,11 +220,13 @@ $conn->close();
|
||||
let childrenModal = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize Tom Select for filters
|
||||
// Initialize Tom Select for filters with allowEmptyOption
|
||||
const tsOptions = {
|
||||
create: false,
|
||||
sortField: { field: "text", direction: "asc" },
|
||||
allowEmptyOption: true,
|
||||
onChange: function(value) {
|
||||
// Propagate change
|
||||
this.input.dispatchEvent(new Event('change'));
|
||||
}
|
||||
};
|
||||
@@ -492,19 +494,6 @@ $conn->close();
|
||||
|
||||
const pliId = child.dataset.itemId;
|
||||
const articleId = child.dataset.articleId;
|
||||
// Check for container data
|
||||
let bpId = null;
|
||||
let bpCompId = null;
|
||||
if (child.querySelector('.backpack-root-item')) {
|
||||
// Assuming we store this in dataset or infer from something?
|
||||
// Actually, the sync relies on existing items usually. But for structure,
|
||||
// we need to persist these IDs if they exist on the element.
|
||||
// Since createPackedItemDOM doesn't attach them to dataset, let's fix that or rely on existing data logic?
|
||||
// Better: attach to dataset in createPackedItemDOM
|
||||
}
|
||||
// Correction: The frontend re-sends the whole tree. If we don't send bpId, it might be lost if backend just upserts.
|
||||
// However, backend 'sync_list' usually recreates or updates.
|
||||
// Let's add data attributes to the DOM first.
|
||||
|
||||
payload.list.push({
|
||||
pli_id: pliId,
|
||||
@@ -530,9 +519,8 @@ $conn->close();
|
||||
// Save scroll positions
|
||||
const scrollPos = {
|
||||
avail: document.getElementById('available-items-list').scrollTop,
|
||||
carrier: document.getElementById('carriers-container').scrollTop // Note: carriers-container itself might not scroll, but the panes do
|
||||
carrier: document.getElementById('carriers-container').scrollTop
|
||||
};
|
||||
// The actual scrolling element is .pane-content
|
||||
const panes = document.querySelectorAll('.pane-content');
|
||||
const scrollMap = Array.from(panes).map(p => p.scrollTop);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user