From 743e5b1c168402c4aac7fb6474c092c8c8b459c6 Mon Sep 17 00:00:00 2001 From: AIIrondev Date: Mon, 10 Aug 2026 23:40:02 +0200 Subject: [PATCH] Reviewed the edeting function --- Web/app.py | 78 ++++++-- Web/templates/library_table.html | 297 +++++++++++++++++-------------- 2 files changed, 226 insertions(+), 149 deletions(-) diff --git a/Web/app.py b/Web/app.py index 979f35b..e29fa2c 100755 --- a/Web/app.py +++ b/Web/app.py @@ -3332,7 +3332,12 @@ def api_library_items(): 'User': 1, 'Ort': 1, 'Beschreibung': 1, - 'Image': 1 + 'Image': 1, + 'SeriesGroupId': 1, + 'SeriesCount': 1, + 'SeriesPosition': 1, + 'IsGroupedSubItem': 1, + 'ParentItemId': 1, } total_count = items_db.count_documents(query) @@ -3360,6 +3365,10 @@ def api_library_items(): 'Beschreibung': 1, 'Image': 1, 'ParentItemId': 1, + 'SeriesGroupId': 1, + 'SeriesCount': 1, + 'SeriesPosition': 1, + 'IsGroupedSubItem': 1, } child_items = list(items_db.find({ 'ParentItemId': {'$in': parent_ids_list}, @@ -3473,6 +3482,48 @@ def api_library_items(): return jsonify({'error': 'An error occurred while fetching library items'}), 500 +@app.route('/api/library_group/') +def api_library_group(series_group_id): + """Fetch all items belonging to one library series group.""" + if 'username' not in session: + return jsonify({'items': []}), 401 + + try: + client = MongoClient(MONGODB_HOST, MONGODB_PORT) + db = client[MONGODB_DB] + items_col = db['items'] + + query = { + 'SeriesGroupId': series_group_id, + 'Deleted': {'$ne': True}, + 'ItemType': {'$in': ['book', 'cd', 'dvd', 'schoolbook', 'schulbuch', 'Buch', 'Schulbuch']}, + } + projection = { + 'Name': 1, + 'ISBN': 1, + 'Code_4': 1, + 'Code4': 1, + 'ItemType': 1, + 'Ort': 1, + 'Beschreibung': 1, + 'SeriesGroupId': 1, + 'SeriesCount': 1, + 'SeriesPosition': 1, + 'IsGroupedSubItem': 1, + 'ParentItemId': 1, + } + + items = list(items_col.find(query, projection).sort([('SeriesPosition', 1), ('Name', 1), ('_id', 1)])) + for item in items: + item['_id'] = str(item['_id']) + + client.close() + return jsonify({'items': items, 'count': len(items), 'series_group_id': series_group_id}) + except Exception as exc: + app.logger.error('Error loading library group %s: %s', series_group_id, exc) + return jsonify({'items': [], 'message': 'Gruppe konnte nicht geladen werden.'}), 500 + + @app.route('/api/library_scan_action', methods=['POST']) def api_library_scan_action(): """ @@ -6336,17 +6387,20 @@ def update_group(): # 1. Shared Fields (Group Logic) # These apply to every item in the group - shared_update = { - 'Name': data.get('name'), - 'Ort': data.get('ort'), - 'Beschreibung': data.get('beschreibung'), - 'Anschaffungsjahr': data.get('ansch_jahr'), - 'Anschaffungskosten': data.get('ansch_kost'), - 'Reservierbar': data.get('reservierbar'), - 'ISBN': data.get('isbn'), - 'ItemType': data.get('item_type'), - 'LastUpdated': datetime.datetime.now() - } + shared_update = {'LastUpdated': datetime.datetime.now()} + for source_key, target_key in ( + ('name', 'Name'), + ('ort', 'Ort'), + ('beschreibung', 'Beschreibung'), + ('ansch_jahr', 'Anschaffungsjahr'), + ('ansch_kost', 'Anschaffungskosten'), + ('reservierbar', 'Reservierbar'), + ('isbn', 'ISBN'), + ('item_type', 'ItemType'), + ): + value = data.get(source_key) + if value is not None: + shared_update[target_key] = value # 2. Individual Updates (Specific Code Logic) # Expected format: [{'id': '...', 'code_4': '...'}, ...] diff --git a/Web/templates/library_table.html b/Web/templates/library_table.html index e2b2a1c..3a9c40b 100644 --- a/Web/templates/library_table.html +++ b/Web/templates/library_table.html @@ -669,6 +669,11 @@ let keyboardScanBuffer = ''; let keyboardLastKeyAt = 0; const KEYBOARD_SCAN_INTERCHAR_MS = 100; // max time between keystrokes to consider them one scan + let editLibraryState = { + itemId: '', + seriesGroupId: '', + groupMembers: [] + }; const canEditLibraryItems = (document.getElementById('libraryTableContainer')?.dataset.canEdit === '1'); @@ -1394,44 +1399,91 @@ }); } - // Edit Modal Form processing const editForm = document.getElementById('editLibraryForm'); if (editForm) { editForm.addEventListener('submit', async function(e) { - e.preventDefault(); + e.preventDefault(); const itemId = document.getElementById('editLibraryItemId').value; + const currentItem = libraryItems.find(i => i._id === itemId); + if (!currentItem) return; - const updatedData = { + const sharedPayload = { name: document.getElementById('editLibraryName').value, item_type: document.getElementById('editLibraryType').value, isbn: document.getElementById('editLibraryIsbn').value, - code_4: document.getElementById('editLibraryCode4').value, ort: document.getElementById('editLibraryLocation').value, - beschreibung: document.getElementById('editLibraryDescription').value + beschreibung: document.getElementById('editLibraryDescription').value, + ansch_jahr: currentItem.Anschaffungsjahr || '', + ansch_kost: currentItem.Anschaffungskosten || '', + reservierbar: currentItem.Reservierbar !== false, }; + const codeInputs = Array.from(document.querySelectorAll('#editLibraryCodesContainer input[data-item-id]')); + const codeByItemId = new Map(codeInputs.map(input => [input.dataset.itemId, (input.value || '').trim()])); + const groupMembers = editLibraryState.groupMembers.length > 0 ? editLibraryState.groupMembers : [currentItem]; + const isGroupedEdit = Boolean(currentItem.SeriesGroupId) && groupMembers.length > 1; + try { - const response = await fetch(`/api/library_item/${itemId}/update`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': '{{ csrf_token }}', - 'X-CSRF-Token': '{{ csrf_token }}' - }, - body: JSON.stringify(updatedData) - }); + if (isGroupedEdit) { + const payload = { + series_group_id: currentItem.SeriesGroupId, + ...sharedPayload, + items: groupMembers.map(member => ({ + id: member._id, + code_4: codeByItemId.get(member._id) || '' + })) + }; - const result = await response.json(); + const response = await fetch('/update_group', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': '{{ csrf_token }}', + 'X-CSRF-Token': '{{ csrf_token }}' + }, + body: JSON.stringify(payload) + }); - if (response.ok && result.ok) { - alert(result.message || 'Medium erfolgreich aktualisiert!'); - closeEditLibraryModal(); - - pagingState.loading = false; - loadLibraryItems(); + const result = await response.json(); + if (response.ok && result.success) { + alert(result.message || 'Gruppe erfolgreich aktualisiert!'); + closeEditLibraryModal(); + pagingState.loading = false; + await loadLibraryItems(); + } else { + alert(result.message || 'Fehler beim Speichern der Gruppenänderungen.'); + } } else { - alert(result.message || 'Fehler beim Speichern der Änderungen.'); + const primaryCodeInput = codeInputs[0]; + const payload = { + name: sharedPayload.name, + item_type: sharedPayload.item_type, + isbn: sharedPayload.isbn, + code_4: primaryCodeInput ? primaryCodeInput.value.trim() : (currentItem.Code_4 || currentItem.Code4 || '').trim(), + ort: sharedPayload.ort, + beschreibung: sharedPayload.beschreibung + }; + + const response = await fetch(`/api/library_item/${itemId}/update`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': '{{ csrf_token }}', + 'X-CSRF-Token': '{{ csrf_token }}' + }, + body: JSON.stringify(payload) + }); + + const result = await response.json(); + if (response.ok && result.ok) { + alert(result.message || 'Medium erfolgreich aktualisiert!'); + closeEditLibraryModal(); + pagingState.loading = false; + await loadLibraryItems(); + } else { + alert(result.message || 'Fehler beim Speichern der Änderungen.'); + } } } catch (error) { console.error('Update failed:', error); @@ -1441,115 +1493,105 @@ } }); - window.openEditLibraryItem = function(itemId) { + async function fetchLibraryGroupMembers(seriesGroupId) { + if (!seriesGroupId) return []; + + try { + const response = await fetch(`/api/library_group/${encodeURIComponent(seriesGroupId)}`); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const payload = await response.json(); + return Array.isArray(payload.items) ? payload.items : []; + } catch (error) { + console.warn('Falling back to loaded library items for group editing:', error); + return (libraryItems || []).filter(item => item.SeriesGroupId === seriesGroupId); + } + } + + function renderLibraryGroupCodeFields(groupMembers, currentItemId) { + const codesContainer = document.getElementById('editLibraryCodesContainer'); + const groupWarning = document.getElementById('editLibraryGroupWarning'); + const groupCount = document.getElementById('editLibraryGroupCount'); + const groupHint = document.getElementById('editLibraryGroupHint'); + + if (!codesContainer) return; + + const items = Array.isArray(groupMembers) ? groupMembers.slice() : []; + items.sort((a, b) => (a.SeriesPosition || 0) - (b.SeriesPosition || 0) || String(a.Name || '').localeCompare(String(b.Name || ''))); + + editLibraryState.groupMembers = items; + + if (groupWarning) { + groupWarning.style.display = items.length > 1 ? 'block' : 'none'; + } + if (groupCount) { + const totalCount = items.length || 1; + const declaredCount = items[0]?.SeriesCount || totalCount; + groupCount.textContent = `${totalCount} / ${declaredCount}`; + } + if (groupHint) { + groupHint.textContent = items.length > 1 + ? 'Jeder Code gehört zu einem eigenen Exemplar. Änderungen werden für alle Codes gespeichert.' + : 'Einzelnes Exemplar. Der Code wird direkt gespeichert.'; + } + + if (!items.length) { + codesContainer.innerHTML = '
Keine Codes geladen.
'; + return; + } + + codesContainer.innerHTML = items.map((member, index) => { + const codeValue = member.Code_4 || member.Code4 || ''; + const labelParts = []; + if (member.SeriesPosition !== undefined && member.SeriesPosition !== null) { + labelParts.push(`Exemplar ${member.SeriesPosition}`); + } else { + labelParts.push(`Exemplar ${index + 1}`); + } + if (member._id === currentItemId) { + labelParts.push('aktuelles Medium'); + } + return ` +
+ + +
+ `; + }).join(''); + } + + window.openEditLibraryItem = async function(itemId) { const item = libraryItems.find(i => i._id === itemId); if (!item) return; + editLibraryState.itemId = item._id; + editLibraryState.seriesGroupId = item.SeriesGroupId || ''; + editLibraryState.groupMembers = []; + // 1. Felder befüllen document.getElementById('editLibraryItemId').value = item._id; document.getElementById('editLibraryName').value = item.Name; document.getElementById('editLibraryType').value = item.ItemType; document.getElementById('editLibraryIsbn').value = item.ISBN || ''; - document.getElementById('editLibraryCode4').value = item.Code_4 || ''; document.getElementById('editLibraryLocation').value = item.Ort; document.getElementById('editLibraryDescription').value = item.Beschreibung; - // 2. Gruppen-Logik - const warningDiv = document.getElementById('editLibraryGroupWarning'); - const codesContainer = document.getElementById('editLibraryAllCodes'); - - if (item.SeriesGroupId) { - // Filtern aus dem aktuell geladenen Array - let groupMembers = libraryItems.filter(i => i.SeriesGroupId === item.SeriesGroupId); - - // SCHLÜSSEL: Wenn die Anzahl der gefundenen Elemente nicht mit SeriesCount übereinstimmt, - // haben wir die Gruppe noch nicht vollständig geladen. - if (groupMembers.length < (item.SeriesCount || 0)) { - console.warn("Gruppe noch nicht vollständig geladen. Anzeige ggf. unvollständig."); - // Optional: Zeige einen Ladehinweis im Modal - codesContainer.textContent = "Lade restliche Gruppenmitglieder..."; - } else { - // Daten sind vollständig -> Anzeigen - const codeList = groupMembers - .sort((a, b) => (a.SeriesPosition || 0) - (b.SeriesPosition || 0)) - .map(m => m.Code_4 || "---") - .join(', '); - - codesContainer.textContent = codeList; - } - - document.getElementById('editLibraryGroupCount').textContent = groupMembers.length + " / " + (item.SeriesCount || "?"); - warningDiv.style.display = 'block'; - } else { - warningDiv.style.display = 'none'; + const codesContainer = document.getElementById('editLibraryCodesContainer'); + if (codesContainer) { + codesContainer.innerHTML = '
Lade Codes...
'; } + const groupMembers = item.SeriesGroupId ? await fetchLibraryGroupMembers(item.SeriesGroupId) : [item]; + renderLibraryGroupCodeFields(groupMembers.length > 0 ? groupMembers : [item], item._id); + document.getElementById('editLibraryModal').style.display = 'flex'; }; function closeEditLibraryModal() { document.getElementById('editLibraryModal').style.display = 'none'; } - - /** - * Event-Listener für das Formular (Initialisierung) - */ - document.addEventListener('DOMContentLoaded', function() { - const editForm = document.getElementById('editLibraryForm'); - if (editForm) { - editForm.addEventListener('submit', async function(e) { - e.preventDefault(); - - const itemId = document.getElementById('editLibraryItemId').value; - const currentItem = libraryItems.find(i => i._id === itemId); - - if (!currentItem) return; - - // 1. Alle Mitglieder der Gruppe finden, um die Code-Liste aufzubauen - const groupMembers = libraryItems.filter(i => i.SeriesGroupId === currentItem.SeriesGroupId); - const individualUpdates = groupMembers.map(member => ({ - id: member._id, - // Wenn dies das bearbeitete Item ist, nimm den neuen Code, sonst den alten - code_4: (member._id === itemId) ? document.getElementById('editLibraryCode4').value : member.Code_4 - })); - - // 2. Payload für das Backend bauen - const payload = { - series_group_id: currentItem.SeriesGroupId, - name: document.getElementById('editLibraryName').value, - ort: document.getElementById('editLibraryLocation').value, - beschreibung: document.getElementById('editLibraryDescription').value, - isbn: document.getElementById('editLibraryIsbn').value, - item_type: document.getElementById('editLibraryType').value, - items: individualUpdates - }; - - // 3. Request an die Gruppen-Update Route - try { - const response = await fetch('/update_group', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload) - }); - - const result = await response.json(); - if (result.success) { - alert('Gruppe erfolgreich synchronisiert!'); - closeEditLibraryModal(); - await loadLibraryItems(); // Daten neu laden - // renderTable(); // Ggf. Tabelle neu rendern - } else { - await loadLibraryItems(); - closeEditLibraryModal(); - } - } catch (error) { - console.error('Update failed:', error); - alert('Netzwerkfehler.'); - } - }); - - } - }); -

- Alle aufgeführten Codes gehören zu diesem Datensatz: +

+ Änderungen an Titel, Ort und Beschreibung werden auf alle Exemplare der Gruppe übertragen.

- - -
- +
- Hinweis: Änderungen an Titel/Ort/Beschreibung werden auf alle Exemplare der Range übertragen. + Hinweis: Jeder Mediencode wird einzeln gespeichert, damit alle Exemplare der Gruppe korrekt bleiben.
@@ -1597,8 +1636,8 @@
- - + +
@@ -1616,20 +1655,4 @@
- {% endblock %} \ No newline at end of file