Feature: Bildbibliothek als Modal in Artikel-Erstellung (bis zu 500 Bilder)
This commit is contained in:
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user