Files
Inventarsystem/Web/templates/edit_library.html
T
Aiirondev_dev 152a3ab135
Release Inventarsystem / release-docker (push) Successful in 2m15s
corrected type mismatch
2026-08-12 14:07:17 +02:00

509 lines
20 KiB
HTML

<!--
Copyright 2025-2026 AIIrondev
Licensed under the Inventarsystem EULA.
-->
{% extends "base.html" %}
{% block title %}{{ page_title|default('Artikel bearbeiten') }} - Inventarsystem{% endblock %}
{% block content %}
<style>
.edit-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: var(--ui-surface);
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.edit-form h1 {
color: var(--ui-text);
margin-bottom: 30px;
text-align: center;
font-size: 2em;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
color: var(--ui-text);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
box-sizing: border-box;
}
.form-group textarea {
height: 100px;
resize: vertical;
}
.filter-inputs {
background-color: var(--ui-surface-soft);
padding: 20px;
border-radius: 5px;
margin: 20px 0;
}
.filter-inputs h3 {
color: var(--ui-text);
margin-bottom: 15px;
font-size: 1.2em;
}
.multi-filter {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.filter-dropdown-select {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
}
.isbn-input-group {
display: flex;
gap: 10px;
align-items: center;
}
.isbn-input-group input {
flex: 1;
}
.fetch-isbn-button {
background-color: #007bff;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
white-space: nowrap;
transition: background-color 0.2s ease;
}
.fetch-isbn-button:hover {
background-color: #0056b3;
}
/* Scanner Elements */
#code4-scanner video, #code4-scanner canvas,
#isbn-scanner video, #isbn-scanner canvas {
width: 100%;
max-width: 640px;
height: auto;
border-radius: 5px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
#code4-scanner canvas, #isbn-scanner canvas {
position: absolute;
top: 0;
left: 0;
}
#code4-scanner, #isbn-scanner {
position: relative;
overflow: hidden;
}
/* Image Management */
.existing-images-grid {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 10px;
}
.existing-image-card {
border: 1px solid #ddd;
border-radius: 5px;
padding: 8px;
text-align: center;
background: #fff;
width: 120px;
}
.existing-image-card img {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 4px;
}
.existing-image-card label {
font-size: 0.8em;
margin-top: 5px;
cursor: pointer;
font-weight: normal;
}
.add-new-btn {
background-color: #007bff;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
margin-top: 5px;
font-size: 12px;
}
.submit-button {
background-color: #28a745;
color: white;
border: none;
padding: 15px 30px;
border-radius: 5px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
width: 100%;
margin-top: 20px;
transition: background-color 0.2s ease;
}
.submit-button:hover {
background-color: #218838;
}
.edit-new-location-container {
display: none;
margin-top: 10px;
}
</style>
<div class="edit-container">
<div class="edit-form">
<h1>{{ page_title|default('Artikel bearbeiten') }}</h1>
<form method="POST" action="{{ url_for('item_edit', id=item._id) }}" enctype="multipart/form-data">
<input type="hidden" name="item_id" value="{{ item._id }}">
{% if show_library_features %}
<!-- ================= LIBRARY SPECIFIC FIELDS ================= -->
<div class="form-group">
<label for="isbn">ISBN / Barcode:</label>
<div class="isbn-input-group">
<input type="text" id="isbn" name="isbn" value="{{ item.ISBN|default('') }}" placeholder="ISBN oder Barcode eingeben...">
<button type="button" id="scan-isbn-btn" class="fetch-isbn-button">Barcode scannen</button>
<button type="button" class="fetch-isbn-button" onclick="fetchBookInfo('edit')">Informationen abrufen</button>
</div>
<div id="isbn-scanner" style="width:100%; max-width:520px; display:none; margin-top:10px;"></div>
<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 CORE FIELDS ================= -->
<div class="form-group">
<label for="name">Name / Titel:</label>
<input type="text" id="name" name="name" value="{{ item.Name|default('') }}" required>
</div>
<div class="form-group">
<label for="ort">Ort / Standort:</label>
<select id="ort" name="ort" data-selected="{{ item.Ort|default('') }}" required>
<option value="">-- Bitte Ort auswählen --</option>
{% if item.Ort %}
<option value="{{ item.Ort }}" selected>{{ item.Ort }}</option>
{% endif %}
</select>
<button type="button" class="add-new-btn" id="add-new-location-btn">Neuen Ort hinzufügen</button>
<div id="new-location-container" class="edit-new-location-container">
<input type="text" id="new-location-input" placeholder="Neuen Ort eingeben">
<button type="button" onclick="addNewLocation()">Hinzufügen</button>
<button type="button" onclick="cancelAddLocation()">Abbrechen</button>
</div>
</div>
<div class="form-group">
<label for="beschreibung">Beschreibung:</label>
<textarea id="beschreibung" name="beschreibung" required>{{ item.Beschreibung|default('') }}</textarea>
</div>
<div class="form-group" id="primary_code_group">
<label for="code_4">Basis-Code (Haupt-Barcode)</label>
<div style="display: flex; gap: 10px;">
<input type="text" id="code_4" name="code_4" class="form-control" value="{{ item.Code_4|default('') }}" required>
<button type="button" id="scan-code4-btn" class="fetch-isbn-button">Barcode scannen</button>
</div>
<div id="code4-scanner" style="display:none; margin-top: 10px;"></div>
<small id="code4-scan-status" class="form-text text-muted"></small>
</div>
<div class="form-group" id="individual_codes_group">
<label for="individual_codes">Weitere Einzelcodes der Gruppe (je Zeile ein Code)</label>
<textarea id="individual_codes" name="individual_codes" rows="4" class="form-control" placeholder="z.B.&#10;ABC-001&#10;ABC-002">{{ item.IndividualCodes|default('') }}</textarea>
<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 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">
{% for idx in range(4) %}
<div class="form-group">
<label for="filter1-{{ idx + 1 }}">Wert {{ idx + 1 }}:</label>
<select id="filter1-{{ idx + 1 }}" name="filter" class="filter-dropdown-select" data-selected="{{ item.Filter[idx] if item.Filter and item.Filter|length > idx else '' }}">
<option value="">-- Optional --</option>
</select>
</div>
{% endfor %}
</div>
<h3>Jahrgangsstufe (Filter 2):</h3>
<div class="multi-filter">
{% for idx in range(4) %}
<div class="form-group">
<label for="filter2-{{ idx + 1 }}">Wert {{ idx + 1 }}:</label>
<select id="filter2-{{ idx + 1 }}" name="filter2" class="filter-dropdown-select" data-selected="{{ item.Filter2[idx] if item.Filter2 and item.Filter2|length > idx else '' }}">
<option value="">-- Optional --</option>
</select>
</div>
{% endfor %}
</div>
<h3>Schlagwort (Filter 3):</h3>
<div class="multi-filter">
{% for idx in range(4) %}
<div class="form-group">
<label for="filter3-{{ idx + 1 }}">Wert {{ idx + 1 }}:</label>
<input type="text" id="filter3-{{ idx + 1 }}" name="filter3" value="{{ item.Filter3[idx] if item.Filter3 and item.Filter3|length > idx else '' }}">
</div>
{% endfor %}
</div>
</div>
{% endif %}
<!-- ================= DATES & FINANCIALS ================= -->
<div class="form-group">
<label for="anschaffungsjahr">Anschaffungsjahr:</label>
<input type="date" id="anschaffungsjahr" name="anschaffungsjahr" value="{{ item.Anschaffungsjahr|default('') }}">
</div>
<div class="form-group">
<label for="anschaffungskosten">Anschaffungskosten (€):</label>
<input type="text" id="anschaffungskosten" name="anschaffungskosten" value="{{ item.Anschaffungskosten|default('') }}">
</div>
{% if not show_library_features %}
<!-- ================= INVENTORY IMAGE MANAGEMENT ================= -->
<div class="form-group">
<label>Bestehende Bilder behalten:</label>
{% if item.Images and item.Images|length > 0 %}
<div class="existing-images-grid">
{% for img in item.Images %}
<div class="existing-image-card">
<img src="{{ url_for('uploaded_file', filename=img) }}" alt="Bild">
<label>
<input type="checkbox" name="existing_images" value="{{ img }}" checked>
Behalten
</label>
</div>
{% endfor %}
</div>
{% else %}
<p style="color:#777; font-size:0.9em;">Keine Bilder vorhanden.</p>
{% endif %}
<label for="images" style="margin-top:15px;">Neue Bilder/Videos hinzufügen:</label>
<input type="file" id="images" name="images" accept=".jpg, .jpeg, .png, .gif, .mp4, .mov, .avi, .mkv, .webm" multiple>
</div>
{% endif %}
<div class="form-group">
<label for="reservierbar" style="display:inline-block; width:auto; margin-right:10px;">Reservierbar:</label>
<input type="checkbox" id="reservierbar" name="reservierbar" style="width:auto;" {% if item.Reservierbar %}checked{% endif %}>
</div>
<button type="submit" class="submit-button">Änderungen speichern</button>
</form>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/@ericblade/quagga2/dist/quagga.js"></script>
<script>
let scannerRunning = false;
let activeScannerCallback = null;
let code4LastScanned = '';
let code4LastScannedAt = 0;
function loadAndSelectFilterValues(filterNumber) {
fetch(`/get_predefined_filter_values/${filterNumber}`)
.then(res => res.json())
.then(data => {
for (let i = 1; i <= 4; i++) {
const select = document.getElementById(`filter${filterNumber}-${i}`);
if (!select) continue;
const selectedValue = select.getAttribute('data-selected') || '';
data.values.forEach(val => {
if (!val || String(val).trim() === '') return;
const opt = document.createElement('option');
opt.value = val;
opt.textContent = val;
select.appendChild(opt);
});
if (selectedValue && !Array.from(select.options).some(o => o.value === selectedValue)) {
const customOpt = document.createElement('option');
customOpt.value = selectedValue;
customOpt.textContent = selectedValue;
select.appendChild(customOpt);
}
select.value = selectedValue;
}
})
.catch(err => console.error(`Error loading Filter ${filterNumber}:`, err));
}
function loadLocationOptions() {
fetch('/get_predefined_locations')
.then(res => res.json())
.then(data => {
const select = document.getElementById('ort');
if (!select) return;
const currentVal = select.getAttribute('data-selected') || select.value;
data.locations.forEach(loc => {
if (!Array.from(select.options).some(o => o.value === loc)) {
const opt = document.createElement('option');
opt.value = loc;
opt.textContent = loc;
select.appendChild(opt);
}
});
select.value = currentVal;
})
.catch(err => console.error('Error loading locations:', err));
}
function addNewLocation() {
const input = document.getElementById('new-location-input');
const val = input.value.trim();
if (!val) return;
const select = document.getElementById('ort');
const opt = document.createElement('option');
opt.value = val;
opt.textContent = val;
opt.selected = true;
select.appendChild(opt);
document.getElementById('new-location-container').style.display = 'none';
input.value = '';
}
function cancelAddLocation() {
document.getElementById('new-location-container').style.display = 'none';
document.getElementById('new-location-input').value = '';
}
function runEngineInitialization(targetSelector, activeCallback, completionMsg, errorStatusSetter) {
if (scannerRunning) { Quagga.stop(); scannerRunning = false; }
activeScannerCallback = activeCallback;
Quagga.init({
inputStream: { name: "Live", type: "LiveStream", target: document.querySelector(targetSelector), constraints: { width: 640, height: 480, facingMode: "environment" } },
decoder: { readers: ["code_128_reader", "ean_reader", "code_39_reader", "upc_reader"] }
}, function(err) {
if (err) { errorStatusSetter("Kamera-Fehler.", true); return; }
Quagga.start(); scannerRunning = true; errorStatusSetter(completionMsg, false);
});
}
function killScannerHardware() {
if (!scannerRunning) return;
Quagga.stop(); scannerRunning = false; activeScannerCallback = null;
}
Quagga.onDetected(function(data) {
if (!data || !data.codeResult || !data.codeResult.code) return;
if (typeof activeScannerCallback === "function") activeScannerCallback(String(data.codeResult.code).trim());
});
function startCode4Scanner() {
const scannerBox = document.getElementById('code4-scanner');
const scanBtn = document.getElementById('scan-code4-btn');
const baseField = document.getElementById('code_4');
const indArea = document.getElementById('individual_codes');
if (scannerBox.style.display !== 'none') {
killScannerHardware(); scannerBox.style.display = 'none'; scanBtn.textContent = 'Barcode scannen'; return;
}
scannerBox.style.display = 'block'; scanBtn.textContent = 'Scanner stoppen';
runEngineInitialization('#code4-scanner', function(decodedText) {
const now = Date.now();
if (decodedText === code4LastScanned && (now - code4LastScannedAt) < 1500) return;
code4LastScanned = decodedText; code4LastScannedAt = now;
killScannerHardware(); scannerBox.style.display = 'none'; scanBtn.textContent = 'Barcode scannen';
if (!baseField.value.trim()) {
baseField.value = decodedText;
} else {
let codes = indArea.value.split('\n').map(c => c.trim()).filter(c => c);
if (!codes.includes(decodedText) && baseField.value.trim() !== decodedText) {
codes.push(decodedText);
indArea.value = codes.join('\n');
}
}
}, 'Scanner läuft...', msg => { document.getElementById('code4-scan-status').textContent = msg; });
}
document.addEventListener('DOMContentLoaded', function() {
loadLocationOptions();
// Load Filter options if elements exist on page
if (document.getElementById('filter1-1')) {
loadAndSelectFilterValues(1);
loadAndSelectFilterValues(2);
}
const scanCodeBtn = document.getElementById('scan-code4-btn');
if (scanCodeBtn) scanCodeBtn.addEventListener('click', startCode4Scanner);
const addLocBtn = document.getElementById('add-new-location-btn');
if (addLocBtn) addLocBtn.addEventListener('click', () => {
const c = document.getElementById('new-location-container');
c.style.display = c.style.display === 'none' ? 'block' : 'none';
});
});
</script>
{% endblock %}