Compare commits

...

6 Commits

Author SHA1 Message Date
Aiirondev_dev 37f514af15 implementation of the edeting function also for the Inventarsystem to complete the transformation from the old outdated system to the new and more refined one
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 14:28:46 +02:00
Aiirondev_dev 152a3ab135 corrected type mismatch
Release Inventarsystem / release-docker (push) Successful in 2m15s
2026-08-12 14:07:17 +02:00
Aiirondev_dev c2c5054814 corrected type mismatch
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 12:40:05 +02:00
Aiirondev_dev e058bd5f46 spelling mistake
Release Inventarsystem / release-docker (push) Successful in 2m14s
2026-08-12 12:30:25 +02:00
Aiirondev_dev d58958db39 Changes to habe he template name right and the right correction for the library Item and a parameter mismatch
Release Inventarsystem / release-docker (push) Successful in 2m22s
2026-08-12 12:26:10 +02:00
Aiirondev_dev 9452743660 Changes to reflekt the right inventar and bibliothek item fields
Release Inventarsystem / release-docker (push) Successful in 2m17s
2026-08-12 11:59:57 +02:00
5 changed files with 74 additions and 854 deletions
+9 -22
View File
@@ -6382,10 +6382,6 @@ def is_library_item(item):
@app.route('/item_edit/<id>', methods=['GET', 'POST'])
def item_edit(id):
"""
Complete endpoint for editing items. Automatically detects whether the item
is a Library item (ItemType != 'other') or an Inventory item (ItemType == 'other').
"""
if 'username' not in session:
if request.method == 'POST' and request.is_json:
return jsonify({'success': False, 'message': 'Nicht angemeldet.'}), 401
@@ -6410,18 +6406,17 @@ def item_edit(id):
flash('Element in der Datenbank nicht gefunden.', 'error')
return redirect(url_for('home_admin'))
# Determine item type classification
# Bibliothek-Status ermitteln
library_module_active = cfg.MODULES.is_enabled('library')
is_lib_item = it.is_library_item(current_item)
show_library_features = library_module_active and is_lib_item
# -------------------------------------------------------------------
# GET METHOD: Render Form
# GET METHOD
# -------------------------------------------------------------------
if request.method == 'GET':
current_item['_id'] = str(current_item['_id'])
# Format individual group codes for the textarea
base_code = current_item.get('Code_4', '')
individual_codes = []
if current_item.get('SeriesGroupId'):
@@ -6445,11 +6440,10 @@ def item_edit(id):
)
# -------------------------------------------------------------------
# POST METHOD: Save Changes
# POST METHOD
# -------------------------------------------------------------------
redirect_target = request.referrer or url_for('home_admin')
# Common fields
name = sanitize_form_value(request.form.get('name'))
ort = sanitize_form_value(request.form.get('ort'))
beschreibung = sanitize_form_value(request.form.get('beschreibung'))
@@ -6457,7 +6451,6 @@ def item_edit(id):
anschaffungs_kosten = sanitize_form_value(request.form.get('anschaffungskosten'))
reservierbar = 'reservierbar' in request.form
# Barcodes
code_4 = sanitize_form_value(request.form.get('code_4'))
individual_codes_raw = request.form.get('individual_codes', '')
@@ -6469,7 +6462,6 @@ def item_edit(id):
all_codes_to_check = [code_4] + individual_codes
# Barcode uniqueness check
current_group_id = current_item.get('SeriesGroupId')
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db_instance = client[cfg.MONGODB_DB]
@@ -6492,29 +6484,27 @@ def item_edit(id):
if has_code_error:
return redirect(redirect_target)
# Type-specific processing
if show_library_features:
# --- LIBRARY ITEM ---
# LIBRARY ITEM: Process ISBN/Medientyp/Category, preserve existing filters
isbn_raw = sanitize_form_value(request.form.get('isbn', ''))
item_isbn = normalize_and_validate_isbn(isbn_raw) if isbn_raw else ''
item_type = sanitize_form_value(request.form.get('item_type_input', 'Buch'))
item_type = sanitize_form_value(request.form.get('item_type_input', current_item.get('ItemType', 'Buch')))
library_category = sanitize_form_value(request.form.get('library_category', ''))
images = current_item.get('Images', [])
filter1 = current_item.get('Filter', [])
filter2 = current_item.get('Filter2', [])
filter3 = current_item.get('Filter3', [])
images = current_item.get('Images', [])
else:
# --- INVENTORY ITEM ---
# NON-LIBRARY (INVENTORY) ITEM: Process Filter 1-3 from form
item_isbn = current_item.get('ISBN', '')
item_type = 'other' # Standard type for inventory items
item_type = 'other'
library_category = current_item.get('library_category', '')
filter1 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter')), 1)
filter2 = expand_filter_selection(sanitize_form_value(request.form.getlist('filter2')), 2)
filter3 = sanitize_form_value(request.form.getlist('filter3'))
# Manage images for Inventory Mode
images_to_keep = request.form.getlist('existing_images')
original_images = current_item.get('Images', [])
images = [img for img in original_images if img in images_to_keep]
@@ -6554,15 +6544,11 @@ def item_edit(id):
images.append(new_filename)
except Exception as e:
app.logger.error(f"Image error for item {id}: {e}")
# Auto-add new location to predefined locations list if applicable
if ort and ort not in it.get_predefined_locations():
it.add_predefined_location(ort)
# Sync series/group barcode IDs
it.sync_group_codes(str(id), code_4, individual_codes)
# Update item in database
success = it.update_item(
id=str(id),
name=name,
@@ -6588,6 +6574,7 @@ def item_edit(id):
flash('Fehler beim Aktualisieren des Artikels.', 'error')
return redirect(redirect_target)
@app.route('/update_group', methods=['POST'])
def update_group():
+29 -16
View File
@@ -29,16 +29,20 @@ import Web.modules.inventarsystem.data_protection as dp
def is_library_item(item):
"""
Determines if an item belongs to the library system.
Returns False for 'other', None, or empty ItemType (Inventory item).
Returns True for any specific library type ('Buch', 'CD', 'DVD', etc.).
Ermittelt zuverlässig, ob ein Objekt zur Bibliothek gehört.
Gibt True zurück, wenn ItemType ein Medientyp ist (Buch, Schulbuch, CD, DVD etc.)
ODER wenn is_library explizit True ist.
"""
if not item:
return False
item_type = item.get('ItemType', 'other')
if not item_type:
return False
return str(item_type).strip().lower() != 'other'
# 1. Prüfe zuerst den Medientyp (ItemType)
item_type = str(item.get('ItemType', '') or '').strip().lower()
if item_type and item_type not in ['other', 'general', 'none', 'null']:
return True
# 2. Falls ItemType 'other' ist, prüfe das is_library Flag
return bool(item.get('is_library', False))
def safe_decrypt_user(encrypted_user):
"""
@@ -264,7 +268,10 @@ def get_group_item_ids(id):
def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter2, filter3,
ansch_jahr, ansch_kost, code_4, reservierbar, isbn=None, item_type='general'):
ansch_jahr, ansch_kost, code_4, reservierbar, isbn="", item_type='other', library_category=""):
"""
Aktualisiert ein Objekt in MongoDB und setzt is_library korrekt basierend auf dem Medientyp.
"""
try:
client = MongoClient(cfg.MONGODB_HOST, cfg.MONGODB_PORT)
db = client[cfg.MONGODB_DB]
@@ -272,29 +279,35 @@ def update_item(id, name, ort, beschreibung, images, verfuegbar, filter1, filter
old_item = items.find_one({'_id': ObjectId(id)})
if not old_item:
client.close()
return False
series_group_id = old_item.get('SeriesGroupId')
# is_library automatisch anhand des neuen item_type bestimmen
is_lib = is_library_item({'ItemType': item_type})
shared_update = {
'Name': name,
'Ort': ort,
'Beschreibung': beschreibung,
'Images': images,
'Filter': filter1,
'Filter2': filter2,
'Filter3': filter3,
'Images': images if isinstance(images, list) else [],
'Filter': filter1 if isinstance(filter1, list) else [],
'Filter2': filter2 if isinstance(filter2, list) else [],
'Filter3': filter3 if isinstance(filter3, list) else [],
'Anschaffungsjahr': ansch_jahr,
'Anschaffungskosten': ansch_kost,
'Reservierbar': reservierbar,
'ISBN': isbn,
'Reservierbar': bool(reservierbar),
'ISBN': str(isbn) if isbn else '',
'ItemType': item_type,
'Verfuegbar': verfuegbar,
'is_library': is_lib,
'library_category': library_category,
'Verfuegbar': bool(verfuegbar),
'LastUpdated': datetime.datetime.now()
}
specific_update = shared_update.copy()
specific_update['Code_4'] = code_4
specific_update['Code_4'] = str(code_4) if code_4 else ''
items.update_one({'_id': ObjectId(id)}, {'$set': specific_update})
-288
View File
@@ -1,288 +0,0 @@
<!--
Copyright 2025-2026 AIIrondev
Licensed under the Inventarsystem EULA (Endbenutzer-Lizenzvertrag).
See Legal/LICENSE for the full license text.
Unauthorized commercial use, SaaS hosting, or removal of branding is prohibited.
For commercial licensing inquiries: https://github.com/AIIrondev
-->
<!-- Edit Item Functions -->
<script>
// Function to check if a file is a video
function isVideoFile(filename) {
const videoExtensions = ['.mp4', '.mov', '.avi', '.mkv', '.webm', '.flv', '.m4v', '.3gp'];
const extension = filename.toLowerCase().substring(filename.lastIndexOf('.'));
return videoExtensions.includes(extension);
}
// Load location options for edit modal
function loadLocationOptions() {
fetch('/get_predefined_locations')
.then(response => response.json())
.then(data => {
const ortSelect = document.getElementById('edit-location');
if (ortSelect) {
// Clear existing options except the first one
while (ortSelect.children.length > 1) {
ortSelect.removeChild(ortSelect.lastChild);
}
// Add new options - data.locations contains the array
data.locations.forEach(location => {
const option = document.createElement('option');
option.value = location;
option.textContent = location;
ortSelect.appendChild(option);
});
}
})
.catch(error => {
console.error('Error loading location options:', error);
});
}
// Edit modal functions
function openEditModal(itemId) {
if (typeof openEditModalFromServer === 'function') {
openEditModalFromServer(itemId);
return;
}
// Find the item data from allItems array
const item = allItems.find(i => i._id === itemId);
if (!item) {
console.error('Item not found:', itemId);
return;
}
// Populate the edit form with current item data
document.getElementById('edit-item-id').value = item._id;
document.getElementById('edit-name').value = item.Name || '';
document.getElementById('edit-description').value = item.Beschreibung || '';
document.getElementById('edit-code4').value = item.Code_4 || '';
document.getElementById('edit-year').value = item.Anschaffungsjahr || '';
document.getElementById('edit-cost').value = item.Anschaffungskosten || '';
// Set reservable status (default to true if undefined)
const reservierbarCheckbox = document.getElementById('edit-reservierbar');
if (reservierbarCheckbox) {
reservierbarCheckbox.checked = item.Reservierbar !== false;
}
// Load location options
loadLocationOptions();
// Set the current location
setTimeout(() => {
const locationSelect = document.getElementById('edit-location');
if (locationSelect && item.Ort) {
locationSelect.value = item.Ort;
}
}, 100);
// Handle filter arrays - set current values
const filter1Array = Array.isArray(item.Filter) ? item.Filter : (item.Filter ? [item.Filter] : []);
const filter2Array = Array.isArray(item.Filter2) ? item.Filter2 : (item.Filter2 ? [item.Filter2] : []);
const filter3Array = Array.isArray(item.Filter3) ? item.Filter3 : (item.Filter3 ? [item.Filter3] : []);
// Set filter dropdowns (up to 4 each)
for (let i = 1; i <= 4; i++) {
// Filter 1
const filter1Select = document.getElementById(`edit-filter1-${i}`);
if (filter1Select) {
filter1Select.value = filter1Array[i-1] || '';
}
// Filter 2
const filter2Select = document.getElementById(`edit-filter2-${i}`);
if (filter2Select) {
filter2Select.value = filter2Array[i-1] || '';
}
// Filter 3
const filter3Select = document.getElementById(`edit-filter3-${i}`);
if (filter3Select) {
filter3Select.value = filter3Array[i-1] || '';
}
}
// Populate existing images
populateExistingImages(item.Images || []);
// Show the modal
document.getElementById('edit-modal').style.display = 'block';
}
function closeEditModal() {
document.getElementById('edit-modal').style.display = 'none';
}
// Function to add new location (for edit modal)
function addNewLocation(prefix) {
// Use different input IDs based on whether we're in edit mode
const inputId = prefix === 'edit' ? 'edit-new-location-input' : 'new-location-input';
const selectId = prefix === 'edit' ? 'edit-location' : 'ort';
const newLocationInput = document.getElementById(inputId);
const newLocation = newLocationInput.value.trim();
if (!newLocation) {
alert('Bitte geben Sie einen Ort ein.');
return;
}
// Add to dropdown
const ortSelect = document.getElementById(selectId);
const option = document.createElement('option');
option.value = newLocation;
option.textContent = newLocation;
ortSelect.appendChild(option);
ortSelect.value = newLocation;
// Hide the input field
document.getElementById(prefix + '-new-location-container').style.display = 'none';
newLocationInput.value = '';
// Save to server
fetch('/add_location_value', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'value=' + encodeURIComponent(newLocation)
})
.then(response => response.json())
.then(data => {
if (!data.success) {
console.warn('Failed to save location to server:', data.error);
}
})
.catch(error => {
console.error('Error saving location:', error);
});
}
// Function to cancel adding a new location
function cancelAddLocation(prefix) {
const containerId = prefix === 'edit' ? 'edit-new-location-container' : 'new-location-container';
const inputId = prefix === 'edit' ? 'edit-new-location-input' : 'new-location-input';
document.getElementById(containerId).style.display = 'none';
document.getElementById(inputId).value = '';
}
// Function to populate existing images in edit modal
function populateExistingImages(images) {
const previewContainer = document.getElementById('edit-image-preview-container');
if (!previewContainer) return;
previewContainer.innerHTML = '';
if (!images || images.length === 0) {
previewContainer.innerHTML = '<p>Keine Bilder vorhanden</p>';
return;
}
images.forEach((imageName, index) => {
const preview = document.createElement('div');
preview.className = 'image-preview-item';
const isVideo = isVideoFile(imageName);
const mediaHtml = isVideo
? `<video src="/uploads/${imageName}" style="max-width: 150px; max-height: 150px; object-fit: cover;" controls preload="metadata"></video>`
: `<img src="/uploads/${imageName}" alt="Image ${index + 1}" style="max-width: 150px; max-height: 150px; object-fit: cover;">`;
preview.innerHTML = `
${mediaHtml}
<div class="image-controls">
<button type="button" onclick="removeExistingImage('${imageName}', this)" style="background: #dc3545; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; margin-left: 10px;">Entfernen</button>
</div>
`;
previewContainer.appendChild(preview);
// Add hidden input for the image
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'existing_images';
hiddenInput.value = imageName;
previewContainer.appendChild(hiddenInput);
});
}
// Function to remove an existing image
function removeExistingImage(imageName, button) {
try {
// First, determine context - are we in edit mode or main view?
const inEditMode = !!document.getElementById('edit-item-form');
// Always remove the preview element (works in both contexts)
const previewItem = button.closest('.image-preview-item');
if (previewItem) {
previewItem.remove();
}
// If we're in edit mode, handle form inputs
if (inEditMode) {
// Find and remove the corresponding hidden input in the edit form
const editForm = document.getElementById('edit-item-form');
if (editForm) {
// Remove from existing images
const existingInputs = editForm.querySelectorAll('input[name="existing_images"]');
existingInputs.forEach(input => {
if (input.value === imageName) {
input.remove();
}
});
// Add to removed images
const removedInput = document.createElement('input');
removedInput.type = 'hidden';
removedInput.name = 'removed_images';
removedInput.value = imageName;
editForm.appendChild(removedInput);
}
} else {
// In main view, we may need different logic
console.log(`Image ${imageName} removed from display in main view`);
// Add any main view specific handling here
}
} catch (error) {
console.error(`Error in removeExistingImage: ${error.message}`);
}
}
// Validate file types for image uploads
document.addEventListener('DOMContentLoaded', function() {
// Check if we're in the edit item context
if (!document.getElementById('edit-item-form')) {
console.log('Edit item form not found, skipping edit item functions initialization');
return; // Exit early if we're not in the edit item context
}
const imageInput = document.getElementById('edit-new-images');
const previewContainer = document.getElementById('edit-image-preview-container');
if (imageInput) {
imageInput.addEventListener('change', function(e) {
// Validate file types before preview
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif',
'video/mp4', 'video/quicktime', 'video/x-msvideo', 'video/x-matroska',
'video/webm', 'video/x-flv', 'video/mp4', 'video/3gpp'];
const files = this.files;
let hasInvalidFile = false;
for (let i = 0; i < files.length; i++) {
if (!allowedTypes.includes(files[i].type)) {
hasInvalidFile = true;
// Clear the file input to prevent submission
this.value = '';
alert('Fehler: Datei "' + files[i].name + '" hat ein nicht unterstütztes Format. Erlaubte Formate: JPG, JPEG, PNG, GIF, MP4, MOV, AVI, MKV, WEBM, FLV, M4V, 3GP');
return; // Stop processing
}
}
// Continue with regular preview handling...
});
}
});
</script>
+24 -30
View File
@@ -103,7 +103,7 @@
background-color: #0056b3;
}
/* Scanner Video Canvas */
/* Scanner Elements */
#code4-scanner video, #code4-scanner canvas,
#isbn-scanner video, #isbn-scanner canvas {
width: 100%;
@@ -197,7 +197,7 @@
<input type="hidden" name="item_id" value="{{ item._id }}">
{% if show_library_features %}
<!-- ================= LIBRARY MODE FIELDS ================= -->
<!-- ================= LIBRARY SPECIFIC FIELDS ================= -->
<div class="form-group">
<label for="isbn">ISBN / Barcode:</label>
<div class="isbn-input-group">
@@ -209,9 +209,26 @@
<small id="isbn-scan-status" style="display:block; color:#666; margin-top:6px;"></small>
<div id="book-info-container"></div>
</div>
<div class="filter-inputs" style="margin-bottom: 20px;">
<h3>Medientyp</h3>
<div class="form-group">
<select name="item_type_input" id="item_type_input">
<option value="Buch" {% if item.ItemType == 'Buch' %}selected{% endif %}>Buch</option>
<option value="Schulbuch" {% if item.ItemType == 'Schulbuch' %}selected{% endif %}>Schulbuch</option>
<option value="CD" {% if item.ItemType == 'CD' %}selected{% endif %}>CD</option>
<option value="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option>
<option value="Sonstiges" {% if item.ItemType == 'Sonstiges' %}selected{% endif %}>Sonstiges</option>
</select>
</div>
<h3>Bibliotheks-Kategorie:</h3>
<div class="form-group">
<input type="text" name="library_category" id="library_category" value="{{ item.library_category|default('') }}" placeholder="z.B. Belletristik, Sachbücher, etc.">
</div>
</div>
{% endif %}
<!-- ================= COMMON FIELDS ================= -->
<!-- ================= COMMON CORE FIELDS ================= -->
<div class="form-group">
<label for="name">Name / Titel:</label>
<input type="text" id="name" name="name" value="{{ item.Name|default('') }}" required>
@@ -254,26 +271,8 @@
<small style="display:block; color:#666; margin-top: 5px;">Der Basis-Code steht oben. Alle weiteren Gruppenmitglieder werden hier untereinander aufgeführt.</small>
</div>
{% if show_library_features %}
<!-- ================= LIBRARY CATEGORIZATION ================= -->
<div class="filter-inputs">
<h3>Medientyp</h3>
<div class="form-group">
<select name="item_type_input" id="item_type_input">
<option value="Buch" {% if item.ItemType == 'Buch' %}selected{% endif %}>Buch</option>
<option value="Schulbuch" {% if item.ItemType == 'Schulbuch' %}selected{% endif %}>Schulbuch</option>
<option value="CD" {% if item.ItemType == 'CD' %}selected{% endif %}>CD</option>
<option value="DVD" {% if item.ItemType == 'DVD' %}selected{% endif %}>DVD</option>
<option value="Sonstiges" {% if item.ItemType == 'Sonstiges' %}selected{% endif %}>Sonstiges</option>
</select>
</div>
<h3>Kategorie / Fach:</h3>
<div class="form-group">
<input type="text" name="library_category" id="library_category" value="{{ item.library_category|default('') }}" placeholder="z.B. Belletristik, Sachbücher, etc.">
</div>
</div>
{% else %}
<!-- ================= INVENTORY FILTERS (1-3) ================= -->
{% if not show_library_features %}
<!-- ================= SYSTEM FILTERS 1-3 (INVENTORY / OTHER ITEMS ONLY) ================= -->
<div class="filter-inputs">
<h3>Unterrichtsfach (Filter 1):</h3>
<div class="multi-filter">
@@ -358,13 +357,11 @@
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
<script>
const libraryModuleEnabled = {{ 'true' if show_library_features else 'false' }};
let scannerRunning = false;
let activeScannerCallback = null;
let code4LastScanned = '';
let code4LastScannedAt = 0;
// Load filter dropdown options and pre-select current item values
function loadAndSelectFilterValues(filterNumber) {
fetch(`/get_predefined_filter_values/${filterNumber}`)
.then(res => res.json())
@@ -383,7 +380,6 @@
select.appendChild(opt);
});
// Add custom option if the item has a value not in predefined list
if (selectedValue && !Array.from(select.options).some(o => o.value === selectedValue)) {
const customOpt = document.createElement('option');
customOpt.value = selectedValue;
@@ -397,7 +393,6 @@
.catch(err => console.error(`Error loading Filter ${filterNumber}:`, err));
}
// Load locations
function loadLocationOptions() {
fetch('/get_predefined_locations')
.then(res => res.json())
@@ -420,7 +415,6 @@
.catch(err => console.error('Error loading locations:', err));
}
// New location handler
function addNewLocation() {
const input = document.getElementById('new-location-input');
const val = input.value.trim();
@@ -440,7 +434,6 @@
document.getElementById('new-location-input').value = '';
}
// Quagga Barcode Scanner Engine
function runEngineInitialization(targetSelector, activeCallback, completionMsg, errorStatusSetter) {
if (scannerRunning) { Quagga.stop(); scannerRunning = false; }
activeScannerCallback = activeCallback;
@@ -497,7 +490,8 @@
document.addEventListener('DOMContentLoaded', function() {
loadLocationOptions();
if (!libraryModuleEnabled) {
// Load Filter options if elements exist on page
if (document.getElementById('filter1-1')) {
loadAndSelectFilterValues(1);
loadAndSelectFilterValues(2);
}
+12 -498
View File
@@ -1951,11 +1951,6 @@
display: none;
}
/* Edit new location container */
.edit-new-location-container {
display: none;
margin-top: 10px;
}
/* Modal dialog styling */
.modal-dialog-white {
@@ -1969,11 +1964,6 @@
margin-top: 10px;
}
/* Element text colors for better visibility */
.edit-button, .duplicate-button, .generate-qr-button {
color: var(--ui-title) !important;
}
/* Standardized button styles across the application */
.search-button, .scan-button, .filter-toggle, .clear-filter,
.add-new-btn, .popup-close-button, .prev-image-button, .next-image-button,
@@ -2474,187 +2464,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
</div>
</div>
</div>
<!-- Add the edit modal form -->
{% if current_permissions.actions.get('can_edit', False) %}
<div id="edit-modal" class="item-modal">
<div class="modal-content">
<span class="close-modal" onclick="closeEditModal()">&times;</span>
<h2>Objekt bearbeiten</h2>
<form id="edit-item-form" method="POST" enctype="multipart/form-data">
<input type="hidden" id="edit-item-id" name="id">
<div class="form-row">
<div class="form-group">
<label for="edit-name">Name:</label>
<input type="text" id="edit-name" name="name" required>
</div>
<div class="form-group">
<label for="edit-location">Ort:</label>
<select id="edit-location" name="ort" required>
<option value="">-- Bitte Ort auswählen --</option>
<!-- Options will be loaded by JavaScript -->
</select>
<button type="button" class="add-new-btn" id="edit-add-new-location-btn">
Neuen Ort hinzufügen
</button>
<div id="edit-new-location-container" class="edit-new-location-container">
<input type="text" id="edit-new-location-input" placeholder="Neuen Ort eingeben">
<button type="button" onclick="addNewLocation('edit')">Hinzufügen</button>
<button type="button" onclick="cancelAddLocation('edit')">Abbrechen</button>
</div>
</div>
</div>
<div class="form-group">
<label for="edit-description">Beschreibung:</label>
<textarea id="edit-description" name="beschreibung" required></textarea>
</div>
<!-- Updated filter inputs for edit form with dropdowns -->
<div class="filter-inputs">
<h3>Unterrichtsfach:</h3>
<div class="multi-filter">
<div class="form-group">
<label for="edit-filter1-1">Wert 1:</label>
<select id="edit-filter1-1" name="filter" class="filter-dropdown-select">
<option value="">-- Bitte auswählen --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter1-2">Wert 2:</label>
<select id="edit-filter1-2" name="filter" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter1-3">Wert 3:</label>
<select id="edit-filter1-3" name="filter" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter1-4">Wert 4:</label>
<select id="edit-filter1-4" name="filter" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
</div>
<h3>Jahrgangsstufe:</h3>
<div class="multi-filter">
<div class="form-group">
<label for="edit-filter2-1">Wert 1:</label>
<select id="edit-filter2-1" name="filter2" class="filter-dropdown-select">
<option value="">-- Bitte auswählen --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter2-2">Wert 2:</label>
<select id="edit-filter2-2" name="filter2" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter2-3">Wert 3:</label>
<select id="edit-filter2-3" name="filter2" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
<div class="form-group">
<label for="edit-filter2-4">Wert 4:</label>
<select id="edit-filter2-4" name="filter2" class="filter-dropdown-select">
<option value="">-- Optional --</option>
<!-- Options will be loaded by JavaScript -->
</select>
</div>
</div>
<h3>Schlagwort:</h3>
<div class="multi-filter">
<div class="form-group">
<label for="edit-filter3-1">Wert 1:</label>
<input type="text" id="edit-filter3-1" name="filter3">
</div>
<div class="form-group">
<label for="edit-filter3-2">Wert 2:</label>
<input type="text" id="edit-filter3-2" name="filter3">
</div>
<div class="form-group">
<label for="edit-filter3-3">Wert 3:</label>
<input type="text" id="edit-filter3-3" name="filter3">
</div>
<div class="form-group">
<label for="edit-filter3-4">Wert 4:</label>
<input type="text" id="edit-filter3-4" name="filter3">
</div>
</div>
</div>
<div class="form-group">
<label for="edit-year">Anschaffungsjahr:</label>
<input type="date" id="edit-year" name="anschaffungsjahr">
</div>
<div class="form-group">
<label for="edit-cost">Anschaffungskosten (€):</label>
<input id="edit-cost" name="anschaffungskosten">
</div>
<div class="form-group">
<label for="edit-code4">Code:</label>
<div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
<input id="edit-code4" name="code_4">
<button type="button" class="fetch-isbn-button" id="scan-edit-code-btn">Barcode scannen</button>
</div>
</div>
<div class="form-group">
<label for="edit-reservierbar" style="display:inline-block; width:auto; margin-right:10px;">Reservierbar:</label>
<input type="checkbox" id="edit-reservierbar" name="reservierbar" style="width:auto;">
<small style="display:block; color:#666;">Wenn deaktiviert, kann der Artikel nicht im Voraus reserviert werden.</small>
</div>
<!-- New section for managing images -->
<div class="form-group">
<label>Vorhandene Bilder:</label>
<div id="edit-existing-images" class="existing-images-container">
<!-- Existing images will be added here dynamically -->
</div>
</div>
<div class="form-group">
<label for="edit-new-images">
<span>Neue Bilder/Videos hinzufügen:</span>
<span>(Bilder/Videos werden vom Original übernommen)</span>
</label>
<input type="file" id="edit-new-images" name="new_images" accept=".jpg, .jpeg, .png, .gif, .mp4, .mov, .avi, .mkv, .webm, .flv, .m4v, .3gp" multiple>
<div class="allowed-formats">Erlaubte Formate: JPG, JPEG, PNG, GIF, MP4, MOV, AVI, MKV, WEBM, FLV, M4V, 3GP</div>
<!-- Add image preview container -->
<div class="image-preview-container" id="edit-image-preview-container"></div>
</div>
<div class="form-group">
<label for="edit-isbn">ISBN:</label>
<div class="isbn-input-group">
<input type="text" id="edit-isbn" name="isbn" placeholder="ISBN eingeben...">
<button type="button" class="fetch-isbn-button" id="scan-edit-isbn-btn">ISBN scannen</button>
<button type="button" class="fetch-isbn-button" onclick="fetchBookInfo('edit')">Buchinformationen abrufen</button>
</div>
<div id="edit-book-info-container" class="book-info-container"></div>
</div>
<div class="form-actions">
<button type="submit" class="save-button">Speichern</button>
<button type="button" class="cancel-button" onclick="closeEditModal()">Abbrechen</button>
</div>
</form>
</div>
</div>
{% endif %}
</div>
<!-- Schedule Appointment Modal -->
@@ -2889,58 +2698,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
});
function scanIntoEditCode() {
const qrReader = document.getElementById('qr-reader');
const editCodeInput = document.getElementById('edit-code4');
const scanEditBtn = document.getElementById('scan-edit-code-btn');
if (!qrReader || !editCodeInput || !scanEditBtn) {
return;
}
// Toggle close if it's already running
if (isScanning && qrReader.style.display !== 'none') {
stopScanner();
scanEditBtn.textContent = 'Barcode scannen';
return;
}
scanEditBtn.textContent = 'Scanner schließen';
// Start scanner with custom logic mapping to the Code input field
startScanner(function(decodedText) {
editCodeInput.value = decodedText;
validateCodeField(editCodeInput, document.getElementById('edit-item-id')?.value || null);
scanEditBtn.textContent = 'Barcode scannen';
});
}
function scanIntoEditIsbn() {
const qrReader = document.getElementById('qr-reader');
const editIsbnInput = document.getElementById('edit-isbn');
const scanIsbnBtn = document.getElementById('scan-edit-isbn-btn');
if (!qrReader || !editIsbnInput || !scanIsbnBtn) {
return;
}
// Toggle close if it's already running
if (isScanning && qrReader.style.display !== 'none') {
stopScanner();
scanIsbnBtn.textContent = 'ISBN scannen';
return;
}
scanIsbnBtn.textContent = 'Scanner schließen';
// Start scanner with custom logic mapping to the ISBN input field
startScanner(function(decodedText) {
editIsbnInput.value = decodedText;
scanIsbnBtn.textContent = 'ISBN scannen';
if (typeof fetchBookInfo === 'function') {
fetchBookInfo('edit');
}
});
}
function rebuildFilter3Options() {
if (!allItems) return;
@@ -3324,21 +3081,11 @@ document.addEventListener('DOMContentLoaded', ()=>{
loadPredefinedFilterValues(1);
loadPredefinedFilterValues(2);
loadPredefinedFilterValues(3);
// Set up edit form submission
setupEditFormSubmission();
// Set up schedule form submission
setupScheduleFormSubmission();
// Set up add new location buttons
const editAddLocationBtn = document.getElementById('edit-add-new-location-btn');
if (editAddLocationBtn) {
editAddLocationBtn.addEventListener('click', function() {
const container = document.getElementById('edit-new-location-container');
container.style.display = container.style.display === 'none' ? 'block' : 'none';
});
}
// Find and attach event listener to all logout links
const logoutLinks = document.querySelectorAll('a[href*="logout"]');
@@ -3391,35 +3138,11 @@ document.addEventListener('DOMContentLoaded', ()=>{
// Close modals when clicking outside
window.onclick = function(event) {
const itemModal = document.getElementById('item-modal');
const editModal = document.getElementById('edit-modal');
if (event.target === itemModal) {
itemModal.style.display = 'none';
}
if (event.target === editModal) {
editModal.style.display = 'none';
}
};
// Set up code validation for edit form
const editCodeField = document.getElementById('edit-code4');
if (editCodeField) {
editCodeField.addEventListener('blur', function() {
const itemIdField = document.getElementById('edit-item-id');
const excludeId = itemIdField ? itemIdField.value : null;
validateCodeField(this, excludeId);
});
}
const scanEditCodeBtn = document.getElementById('scan-edit-code-btn');
if (scanEditCodeBtn) {
scanEditCodeBtn.addEventListener('click', scanIntoEditCode);
}
const scanEditIsbnBtn = document.getElementById('scan-edit-isbn-btn');
if (scanEditIsbnBtn) {
scanEditIsbnBtn.addEventListener('click', scanIntoEditIsbn);
}
});
// Function to load items from server
@@ -4171,163 +3894,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
});
}
function openEditModalForSelectedUnit(defaultItemId, selectId) {
let targetItemId = defaultItemId;
try {
const selectedUnit = selectId ? document.getElementById(selectId) : null;
if (selectedUnit && selectedUnit.value) {
targetItemId = selectedUnit.value;
}
} catch (e) {
// Keep default item id as fallback.
}
openEditModalFromServer(targetItemId);
}
function closeEditModal() {
const editModal = document.getElementById('edit-modal');
if (editModal) {
editModal.style.display = 'none';
}
}
function openEditModalFromServer(itemId) {
const editModal = document.getElementById('edit-modal');
if (!editModal) {
console.error('Edit modal nicht gefunden');
return;
}
console.log('DEBUG: openEditModal called with itemId:', itemId);
// Fetch the item data from the backend
fetch(`/get_item/${itemId}`)
.then(response => {
console.log('DEBUG: Response status:', response.status);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(data => {
console.log('DEBUG: Fetched data:', data);
// Backend returns the item directly or wrapped in error/success
const item = data.error ? null : (data.item || data);
console.log('DEBUG: Parsed item:', item);
if (!item || !item._id) {
console.error('DEBUG: Item not found or invalid');
alert('Item nicht gefunden');
return;
}
// Fill in the form fields with the item data
document.getElementById('edit-item-id').value = item._id || '';
document.getElementById('edit-name').value = item.Name || '';
document.getElementById('edit-location').value = item.Ort || '';
document.getElementById('edit-description').value = item.Beschreibung || '';
document.getElementById('edit-year').value = item.Anschaffungsjahr || '';
document.getElementById('edit-cost').value = item.Anschaffungskosten || '';
document.getElementById('edit-code4').value = item.Code_4 || '';
document.getElementById('edit-isbn').value = item.ISBN || '';
document.getElementById('edit-reservierbar').checked = item.Reservierbar !== false;
// Fill in filter 1 (Unterrichtsfach)
const filter1Array = Array.isArray(item.Filter) ? item.Filter : (item.Filter ? [item.Filter] : []);
document.getElementById('edit-filter1-1').value = filter1Array[0] || '';
document.getElementById('edit-filter1-2').value = filter1Array[1] || '';
document.getElementById('edit-filter1-3').value = filter1Array[2] || '';
document.getElementById('edit-filter1-4').value = filter1Array[3] || '';
// Fill in filter 2 (Jahrgangsstufe)
const filter2Array = Array.isArray(item.Filter2) ? item.Filter2 : (item.Filter2 ? [item.Filter2] : []);
document.getElementById('edit-filter2-1').value = filter2Array[0] || '';
document.getElementById('edit-filter2-2').value = filter2Array[1] || '';
document.getElementById('edit-filter2-3').value = filter2Array[2] || '';
document.getElementById('edit-filter2-4').value = filter2Array[3] || '';
// Fill in filter 3 (Schlagwort)
const filter3Array = Array.isArray(item.Filter3) ? item.Filter3 : (item.Filter3 ? [item.Filter3] : []);
document.getElementById('edit-filter3-1').value = filter3Array[0] || '';
document.getElementById('edit-filter3-2').value = filter3Array[1] || '';
document.getElementById('edit-filter3-3').value = filter3Array[2] || '';
document.getElementById('edit-filter3-4').value = filter3Array[3] || '';
// Display existing images
const existingImagesContainer = document.getElementById('edit-existing-images');
const editForm = document.getElementById('edit-item-form');
existingImagesContainer.innerHTML = '';
if (editForm) {
editForm.querySelectorAll('input[name="existing_images"], input[name="removed_images"]').forEach(input => input.remove());
}
if (item.Images && Array.isArray(item.Images)) {
item.Images.forEach((image, index) => {
const isVideo = isVideoFile(image);
const imageDiv = document.createElement('div');
imageDiv.className = 'existing-image-item';
imageDiv.style.marginBottom = '10px';
const thumbnailInfo = item.ThumbnailInfo && item.ThumbnailInfo[index];
const imageSrc = thumbnailInfo && thumbnailInfo.has_preview ?
thumbnailInfo.preview_url :
(image.startsWith('/uploads/') || image.startsWith('http') ?
image :
`{{ url_for('uploaded_file', filename='') }}${image}`);
const row = document.createElement('div');
row.style.display = 'flex';
row.style.gap = '8px';
row.style.alignItems = 'center';
if (isVideo) {
const video = document.createElement('video');
video.src = imageSrc;
video.style.maxWidth = '100px';
video.style.maxHeight = '100px';
video.style.objectFit = 'contain';
video.controls = true;
row.appendChild(video);
} else {
const img = document.createElement('img');
img.src = imageSrc;
img.style.maxWidth = '100px';
img.style.maxHeight = '100px';
img.style.objectFit = 'contain';
img.alt = `Existierendes Bild ${index + 1}`;
row.appendChild(img);
}
const deleteButton = document.createElement('button');
deleteButton.type = 'button';
deleteButton.className = 'delete-image-button';
deleteButton.textContent = 'Löschen';
deleteButton.addEventListener('click', () => removeExistingImage(image, deleteButton));
row.appendChild(deleteButton);
imageDiv.appendChild(row);
existingImagesContainer.appendChild(imageDiv);
if (editForm) {
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.name = 'existing_images';
hiddenInput.value = image;
editForm.appendChild(hiddenInput);
}
});
}
// Display the modal
editModal.style.display = 'block';
})
.catch(error => {
console.error('Fehler beim Laden des Items:', error);
alert('Fehler beim Laden des Items');
});
}
function escapeHtml(value) {
return String(value ?? '').replace(/[&<>'"]/g, (char) => {
const map = {
@@ -4687,7 +4253,9 @@ document.addEventListener('DOMContentLoaded', ()=>{
<button type="button" class="details-button" onclick="document.getElementById('item-modal').style.display='none';">Schließen</button>
</div>
`;
modal.style.display = 'block';
const closeButton = modal.querySelector('.close-modal');
@@ -4924,6 +4492,13 @@ document.addEventListener('DOMContentLoaded', ()=>{
});
}
function openEditModalForSelectedUnit(itemId, selectId) {
const select = document.getElementById(selectId);
const targetId = (select && select.value) ? select.value : itemId;
// Leitet auf die Bearbeiten-Seite weiter und übergibt die aktuelle URL für den Redirect nach dem Speichern
window.location.href = `/item_edit/${targetId}`;
}
function changeModalImage(direction) {
const currentIndex = window.currentModalImageIndex;
const total = window.totalModalImages;
@@ -5350,8 +4925,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
});
}
// Load location options for edit modal
// Edit-related functions moved to edit_item_functions.html
// Schedule modal functions
function openScheduleModal(itemId) {
@@ -5471,50 +5044,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
}
// Setup edit form submission
function setupEditFormSubmission() {
const editForm = document.getElementById('edit-item-form');
if (editForm) {
editForm.addEventListener('submit', function(e) {
e.preventDefault();
const itemId = document.getElementById('edit-item-id').value;
const formData = new FormData(this);
fetch(`/edit_item/${itemId}`, {
method: 'POST',
body: formData
})
.then(response => {
if (response.ok) {
closeEditModal();
// Reload items to show updated information
loadItems();
// Show success message
const successMsg = document.createElement('div');
successMsg.className = 'alert alert-success';
successMsg.textContent = 'Item wurde erfolgreich aktualisiert!';
successMsg.style.position = 'fixed';
successMsg.style.top = '20px';
successMsg.style.right = '20px';
successMsg.style.zIndex = '9999';
document.body.appendChild(successMsg);
setTimeout(() => {
if (successMsg.parentNode) {
successMsg.parentNode.removeChild(successMsg);
}
}, 3000);
} else {
alert('Fehler beim Aktualisieren des Items');
}
})
.catch(error => {
console.error('Error updating item:', error);
alert('Fehler beim Aktualisieren des Items');
});
});
}
}
// Duplication function
function duplicateItem(itemId) {
@@ -5690,9 +5219,6 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
</script>
<!-- Include edit item functions -->
{% include "edit_item_functions.html" %}
<!-- Include reset item functions -->
{% include "reset_item_functions.html" %}
@@ -5920,16 +5446,4 @@ document.addEventListener('DOMContentLoaded', ()=>{
}
}
</style>
{% if open_item %}
<script>
document.addEventListener("DOMContentLoaded", function() {
if (typeof openEditModalFromServer === 'function') {
setTimeout(function() {
openEditModalFromServer('{{ open_item }}');
}, 500);
}
});
</script>
{% endif %}
{% endblock %}