diff --git a/Web/app.py b/Web/app.py index 3d0365f..5ba7f25 100755 --- a/Web/app.py +++ b/Web/app.py @@ -4064,6 +4064,9 @@ def get_items(): 'Filter3': 1, 'Ort': 1, 'User': 1, + 'BlockedNow': 1, + 'Reservierbar': 1, + 'HasDamage': 1, 'ItemType': 1, } diff --git a/Web/templates/main.html b/Web/templates/main.html index 170b19c..9eacd98 100755 --- a/Web/templates/main.html +++ b/Web/templates/main.html @@ -799,10 +799,8 @@ } function loadItems(offset = 0, append = false) { - // Für Pages nach der ersten: Explizit vollständige Daten laden (light_mode=false) - // Erste Page: light_mode wird automatisch enablet - const lightModeParam = offset > 0 ? '&light_mode=false' : ''; - return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}${lightModeParam}`) + // Keep list payload lightweight; full details are fetched on-demand in openItemQuick. + return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ITEMS_PAGE_SIZE}&light_mode=true`) .then(response => response.json()) .then(data => { const itemsContainer = document.querySelector('#items-container'); @@ -1073,12 +1071,9 @@ // Stop event from bubbling up e.stopPropagation(); - - // Get full item data with correct image paths - const modalItemData = {...item}; - modalItemData.Images = item.Images ? item.Images.map(img => "{{ url_for('uploaded_file', filename='') }}" + img) : []; - - openItemModal(modalItemData); + + // Always load full, up-to-date item details for the modal. + openItemQuick(item._id); }); } } catch (err) { @@ -1414,6 +1409,76 @@ } function openItemModal(item) { + const escapeHtml = (value) => String(value ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + const formatHistoryDate = (value) => { + if (!value) return '-'; + const dt = new Date(value); + if (Number.isNaN(dt.getTime())) { + return escapeHtml(value); + } + return dt.toLocaleString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); + }; + + const damageReports = Array.isArray(item.DamageReports) ? item.DamageReports : []; + const damageRepairs = Array.isArray(item.DamageRepairs) ? item.DamageRepairs : []; + const damageHistoryEntries = []; + + damageReports.forEach((report) => { + damageHistoryEntries.push({ + type: 'report', + rawDate: report?.reported_at || null, + dateLabel: formatHistoryDate(report?.reported_at), + actor: escapeHtml(report?.reported_by || '-'), + description: escapeHtml(report?.description || 'Schaden gemeldet'), + meta: '', + }); + }); + + damageRepairs.forEach((repair) => { + const resolvedReports = Array.isArray(repair?.resolved_reports) ? repair.resolved_reports : []; + damageHistoryEntries.push({ + type: 'repair', + rawDate: repair?.repaired_at || null, + dateLabel: formatHistoryDate(repair?.repaired_at), + actor: escapeHtml(repair?.repaired_by || '-'), + description: 'Als repariert markiert', + meta: `${resolvedReports.length} Meldung(en) abgeschlossen`, + }); + }); + + damageHistoryEntries.sort((a, b) => { + const ta = a.rawDate ? new Date(a.rawDate).getTime() : 0; + const tb = b.rawDate ? new Date(b.rawDate).getTime() : 0; + return (Number.isNaN(tb) ? 0 : tb) - (Number.isNaN(ta) ? 0 : ta); + }); + + const damageHistoryHtml = damageHistoryEntries.length + ? damageHistoryEntries.map((entry) => { + const badgeStyle = entry.type === 'repair' + ? 'background:#dcfce7;color:#166534;' + : 'background:#fee2e2;color:#991b1b;'; + const badgeText = entry.type === 'repair' ? 'Repariert' : 'Schaden'; + const metaLine = entry.meta ? `
${escapeHtml(entry.meta)}
` : ''; + return ` +
+
+ ${badgeText} + ${entry.dateLabel} +
+
Von: ${entry.actor}
+
${entry.description}
+ ${metaLine} +
+ `; + }).join('') + : '
Keine Beschädigungs-Historie vorhanden.
'; + // Get modal elements const modal = document.getElementById('item-modal'); const modalContent = document.getElementById('modal-content-wrapper'); @@ -1571,6 +1636,19 @@
${item.Beschreibung || '-'}
+
+
Beschädigungs-Historie
+
+ + +
+
+
Verfügbarkeit prüfen
@@ -1687,6 +1765,9 @@ const detailsPanel = document.getElementById('calendar-day-details'); const detailsDate = document.getElementById('cal-details-date'); const detailsList = document.getElementById('cal-details-list'); + const damageToggleBtn = document.getElementById('toggle-damage-history'); + const damageHistoryPanel = document.getElementById('damage-history-panel'); + const damageToggleText = document.getElementById('toggle-damage-history-text'); let bookings = []; let currentDate = new Date(); @@ -1847,6 +1928,16 @@ renderCalendar(); }); + damageToggleBtn?.addEventListener('click', () => { + const shouldOpen = damageHistoryPanel.style.display === 'none'; + damageHistoryPanel.style.display = shouldOpen ? 'block' : 'none'; + if (damageToggleText) { + damageToggleText.textContent = shouldOpen ? 'Historie verbergen' : 'Historie anzeigen'; + } + damageToggleBtn.style.background = shouldOpen ? '#e0e7ff' : '#f3f4f6'; + damageToggleBtn.style.borderColor = shouldOpen ? '#818cf8' : '#d1d5db'; + }); + // Availability checker (user) const availDate = document.getElementById('avail-date'); const availStart = document.getElementById('avail-start'); diff --git a/Web/templates/main_admin.html b/Web/templates/main_admin.html index 153c0ef..967dc98 100755 --- a/Web/templates/main_admin.html +++ b/Web/templates/main_admin.html @@ -3415,10 +3415,8 @@ document.addEventListener('DOMContentLoaded', ()=>{ } function loadItems(offset = 0, append = false) { - // Für Pages nach der ersten: Explizit vollständige Daten laden (light_mode=false) - // Erste Page: light_mode wird automatisch enablet - const lightModeParam = offset > 0 ? '&light_mode=false' : ''; - return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}${lightModeParam}`) + // Keep list payload lightweight; full details are fetched on-demand in openItemQuick. + return fetch(`{{ url_for('get_items') }}?offset=${offset}&limit=${MAIN_ADMIN_ITEMS_PAGE_SIZE}&light_mode=true`) .then(response => response.json()) .then(data => { const itemsContainer = document.querySelector('#items-container'); @@ -3592,6 +3590,7 @@ document.addEventListener('DOMContentLoaded', ()=>{ } } const damageCount = Array.isArray(item.DamageReports) ? item.DamageReports.length : 0; + const hasDamage = Boolean(item.HasDamage) || damageCount > 0; const groupedCount = Number(item.GroupedDisplayCount || 1); const availableGroupedCount = Number(item.AvailableGroupedCount ?? (item.Verfuegbar ? 1 : 0)); const isGroupedItem = groupedCount > 1; @@ -3608,7 +3607,7 @@ document.addEventListener('DOMContentLoaded', ()=>{

Thema: ${filter3Display}${filter3More}

Barcode: ${item.Code_4 || '-'}

Anzahl: ${groupedCount}

- ${damageCount > 0 ? `
Schäden gemeldet: ${damageCount}
` : ''} + ${hasDamage ? `
${damageCount > 0 ? `Schäden gemeldet: ${damageCount}` : 'Schäden gemeldet'}
` : ''}
${imagesHtml}
@@ -3680,11 +3679,8 @@ document.addEventListener('DOMContentLoaded', ()=>{ } e.stopPropagation(); - - const modalItemData = {...item}; - modalItemData.Images = item.Images ? item.Images.map(img => "{{ url_for('uploaded_file', filename='') }}" + img) : []; - - openItemModal(modalItemData); + + openItemQuick(item._id); }); } } catch (err) { @@ -4303,14 +4299,58 @@ document.addEventListener('DOMContentLoaded', ()=>{ } const damageReports = Array.isArray(item.DamageReports) ? item.DamageReports : []; - const damageInfoHtml = damageReports.length > 0 - ? `` - : 'Keine Schäden erfasst.'; + const damageRepairs = Array.isArray(item.DamageRepairs) ? item.DamageRepairs : []; + const damageHistoryEntries = []; + + damageReports.forEach(report => { + damageHistoryEntries.push({ + type: 'report', + timestamp: report?.reported_at || null, + dateLabel: escapeHtml(formatDamageTimestamp(report?.reported_at)), + actor: escapeHtml(report?.reported_by || 'Unbekannt'), + description: escapeHtml(report?.description || 'Schaden gemeldet'), + meta: '', + }); + }); + + damageRepairs.forEach(repair => { + const resolvedReports = Array.isArray(repair?.resolved_reports) ? repair.resolved_reports : []; + damageHistoryEntries.push({ + type: 'repair', + timestamp: repair?.repaired_at || null, + dateLabel: escapeHtml(formatDamageTimestamp(repair?.repaired_at)), + actor: escapeHtml(repair?.repaired_by || 'Unbekannt'), + description: 'Als repariert markiert', + meta: `${resolvedReports.length} Meldung(en) abgeschlossen`, + }); + }); + + damageHistoryEntries.sort((a, b) => { + const ta = a.timestamp ? new Date(a.timestamp).getTime() : 0; + const tb = b.timestamp ? new Date(b.timestamp).getTime() : 0; + return (Number.isNaN(tb) ? 0 : tb) - (Number.isNaN(ta) ? 0 : ta); + }); + + const damageHistoryHtml = damageHistoryEntries.length > 0 + ? damageHistoryEntries.map(entry => { + const badgeStyle = entry.type === 'repair' + ? 'background:#dcfce7;color:#166534;' + : 'background:#fee2e2;color:#991b1b;'; + const badgeText = entry.type === 'repair' ? 'Repariert' : 'Schaden'; + const metaLine = entry.meta ? `
${escapeHtml(entry.meta)}
` : ''; + return ` +
+
+ ${badgeText} + ${entry.dateLabel} +
+
Von: ${entry.actor}
+
${entry.description}
+ ${metaLine} +
+ `; + }).join('') + : '
Keine Beschädigungs-Historie vorhanden.
'; modalContent.innerHTML = `

${item.Name}

@@ -4385,9 +4425,17 @@ document.addEventListener('DOMContentLoaded', ()=>{
${item.Beschreibung || '-'}
-
-
Schäden:
-
${damageInfoHtml}
+
+
Beschädigungs-Historie
+
+ + +
@@ -4486,6 +4534,9 @@ document.addEventListener('DOMContentLoaded', ()=>{ const detailsPanel = document.getElementById('calendar-day-details'); const detailsDate = document.getElementById('cal-details-date'); const detailsList = document.getElementById('cal-details-list'); + const damageToggleBtn = document.getElementById('toggle-damage-history'); + const damageHistoryPanel = document.getElementById('damage-history-panel'); + const damageToggleText = document.getElementById('toggle-damage-history-text'); let bookings = []; let currentDate = new Date(); @@ -4645,6 +4696,16 @@ document.addEventListener('DOMContentLoaded', ()=>{ renderCalendar(); }); + damageToggleBtn?.addEventListener('click', () => { + const shouldOpen = damageHistoryPanel.style.display === 'none'; + damageHistoryPanel.style.display = shouldOpen ? 'block' : 'none'; + if (damageToggleText) { + damageToggleText.textContent = shouldOpen ? 'Historie verbergen' : 'Historie anzeigen'; + } + damageToggleBtn.style.background = shouldOpen ? '#e0e7ff' : '#f3f4f6'; + damageToggleBtn.style.borderColor = shouldOpen ? '#818cf8' : '#d1d5db'; + }); + // Availability checker const availDate = document.getElementById('avail-date');