Feature: Bildbibliothek als Modal in Artikel-Erstellung (bis zu 500 Bilder)

This commit is contained in:
Gemini Agent
2025-12-06 17:28:07 +00:00
parent f7733dfce5
commit 36660fdd51

View File

@@ -1,6 +1,6 @@
<?php
// add_article.php - Formular zum Hinzufügen eines neuen Artikels
// FINALE, VOLLSTÄNDIGE VERSION mit Bilder-Auswahl
// FINALE, VOLLSTÄNDIGE VERSION mit Bilder-Bibliothek Modal
$page_title = "Neuen Artikel hinzufügen";
@@ -74,11 +74,11 @@ $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");
// Lade ALLE Bilder (für Auswahl im Modal) - Limit auf 500 für Performance, aber scrollbar
$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 500");
$stmt_imgs->bind_param($types, ...$household_member_ids);
$stmt_imgs->execute();
$recent_images = $stmt_imgs->get_result()->fetch_all(MYSQLI_ASSOC);
$all_images = $stmt_imgs->get_result()->fetch_all(MYSQLI_ASSOC);
$stmt_imgs->close();
// Strukturierung Lagerorte
@@ -266,19 +266,12 @@ $conn->close();
<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)): ?>
<!-- New: Button to open modal -->
<?php if(!empty($all_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>
<button type="button" class="btn btn-outline-secondary w-100" data-bs-toggle="modal" data-bs-target="#imageLibraryModal">
<i class="fas fa-images me-2"></i>Aus vorhandenen Bildern wählen
</button>
</div>
<?php endif; ?>
@@ -297,7 +290,80 @@ $conn->close();
</form>
</div>
</div>
<!-- Image Library Modal -->
<div class="modal fade" id="imageLibraryModal" tabindex="-1" aria-labelledby="imageLibraryModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageLibraryModalLabel">Bildbibliothek</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Schließen"></button>
</div>
<div class="modal-body bg-light">
<p class="small text-muted mb-2">Wähle ein Bild aus deinem bisherigen Bestand:</p>
<div class="d-flex flex-wrap gap-2 justify-content-center">
<?php if(!empty($all_images)): ?>
<?php foreach($all_images as $img): ?>
<div class="library-image-container" onclick="selectLibraryImage('<?php echo htmlspecialchars($img['image_url']); ?>')">
<img src="<?php echo htmlspecialchars($img['image_url']); ?>" loading="lazy">
</div>
<?php endforeach; ?>
<?php else: ?>
<p class="text-muted">Keine Bilder gefunden.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<style>
.library-image-container {
width: 100px;
height: 100px;
border: 2px solid transparent;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
background: white;
transition: transform 0.1s, border-color 0.1s;
}
.library-image-container:hover {
border-color: var(--color-primary);
transform: scale(1.05);
z-index: 10;
}
.library-image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
<script>
// Global function for onclick event in modal
function selectLibraryImage(url) {
const imagePreview = document.getElementById('imagePreview');
const selectedExistingInput = document.getElementById('selected_existing_image');
const imageFileInput = document.getElementById('image_file');
const imageUrlInput = document.getElementById('image_url');
const pastedImageDataInput = document.getElementById('pasted_image_data');
// Update Preview & Input
imagePreview.src = url;
selectedExistingInput.value = url;
// Clear other inputs
imageFileInput.value = '';
imageUrlInput.value = '';
pastedImageDataInput.value = '';
// Close Modal
const modalEl = document.getElementById('imageLibraryModal');
const modal = bootstrap.Modal.getInstance(modalEl);
modal.hide();
}
document.addEventListener('DOMContentLoaded', function() {
function setupAddNewOption(selectId, containerId) {
const select = document.getElementById(selectId);
@@ -305,19 +371,14 @@ document.addEventListener('DOMContentLoaded', function() {
if (!select || !container) return;
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;
}
const toggleMan = setupAddNewOption('manufacturer_id', 'new_manufacturer_container');
@@ -329,16 +390,12 @@ document.addEventListener('DOMContentLoaded', function() {
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) {
@@ -352,17 +409,6 @@ 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();
@@ -383,7 +429,6 @@ 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());
}
@@ -399,7 +444,7 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
// Initialize Tom Select for searchable dropdowns
// Initialize Tom Select
const tsOptionsBase = {
create: false,
sortField: { field: "text", direction: "asc" }