(function () { const API_BASE = '/api'; const printerSelect = document.getElementById('printerSelect'); const printerCompare = document.getElementById('printerCompare'); const matBody = document.getElementById('matBody'); const tableHead = document.getElementById('tableHead'); const statusEl = document.getElementById('status'); const errorBox = document.getElementById('errorBox'); const tooltipEl = document.getElementById('mmTooltip'); if (!printerSelect || !printerCompare || !matBody || !tableHead) { return; } function setStatus(text) { if (statusEl) { statusEl.textContent = text; } } function showError(msg) { if (!errorBox) return; errorBox.hidden = false; errorBox.textContent = msg; } function clearError() { if (!errorBox) return; errorBox.hidden = true; errorBox.textContent = ''; } function escapeHtml(value) { return String(value ?? '') .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async function fetchJSON(path) { const res = await fetch(API_BASE + path, { headers: { 'Accept': 'application/json' } }); if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + path); return await res.json(); } async function loadPrinters() { try { setStatus('Lade Drucker ...'); const data = await fetchJSON('/printers'); printerSelect.innerHTML = ''; printerCompare.innerHTML = ''; if (!Array.isArray(data) || data.length === 0) { printerSelect.innerHTML = ''; setStatus('Keine Drucker gefunden'); return; } data.forEach(p => { printerSelect.appendChild(new Option(p.name, p.id)); printerCompare.appendChild(new Option(p.name, p.id)); }); loadSinglePrinter(data[0].id); setStatus('Drucker geladen'); } catch (err) { console.error(err); showError('Konnte Drucker nicht laden.'); setStatus('Fehler'); } } async function loadSinglePrinter(id) { if (!id) return; clearError(); setStatus('Lade Materialien ...'); try { const data = await fetchJSON('/printer-materials?id=' + encodeURIComponent(id)); renderTable([data]); setStatus('Fertig'); } catch (err) { console.error(err); showError('Konnte Materialien für den Drucker nicht laden.'); setStatus('Fehler'); } } async function loadMultiplePrinters(ids) { if (!ids.length) return; clearError(); setStatus('Lade Vergleich ...'); try { const datasets = await Promise.all( ids.map(id => fetchJSON('/printer-materials?id=' + encodeURIComponent(id))) ); renderTable(datasets); setStatus('Vergleich geladen'); } catch (err) { console.error(err); showError('Konnte einen der gewählten Drucker nicht laden.'); setStatus('Fehler'); } } function renderTable(datasets) { const baseHead = [ 'Material', 'Eigenschaften', 'Anwendung', 'Kinder ', 'Emission ' ].map(label => `${label}`).join(''); let printerCols = ''; datasets.forEach(ds => { if (ds && ds.printer) { printerCols += `${escapeHtml(ds.printer.name)}`; } }); tableHead.innerHTML = baseHead + printerCols; const materials = (datasets[0] && datasets[0].materials) ? datasets[0].materials : []; matBody.innerHTML = ''; if (!materials.length) { const empty = document.createElement('tr'); empty.innerHTML = 'Keine Materialien gefunden.'; matBody.appendChild(empty); return; } materials.forEach((m, idx) => { const tr = document.createElement('tr'); tr.className = idx % 2 === 0 ? '' : 'is-alt'; const kid = m.kid_safety === 'safe' ? { icon: '🌿', text: 'sicher' } : (m.kid_safety === 'limited' ? { icon: '🟡', text: 'eingeschränkt' } : { icon: '🔴', text: 'nicht geeignet' }); const em = m.emission === 'low' ? { icon: '✅', text: 'niedrig' } : (m.emission === 'medium' ? { icon: '⚠️', text: 'mittel' } : { icon: '⛔', text: 'hoch' }); let html = ''; const detailPayload = { tg: m.tg_celsius ?? '–', nozzle: m.nozzle_req ?? '–', plate: m.plate_req ?? '–', extra: m.extra_req ?? '–', emission: m.emission ?? '–' }; const detailAttr = escapeHtml(JSON.stringify(detailPayload)); html += `` + `${escapeHtml(m.code)}` + `
${escapeHtml(m.short_desc || '')}
` + `` + ``; html += `${escapeHtml(m.properties || '')}`; html += `${escapeHtml(m.application || '')}`; html += `${kid.icon}`; html += `${em.icon}`; datasets.forEach(ds => { const printerId = ds && ds.printer ? ds.printer.id : ''; const match = ds.materials.find(x => x.id === m.id || x.code === m.code); if (!match || !match.support_level) { html += `unbekannt`; } else { let badge = ''; if (match.support_level === 'full') { badge = 'voll'; } else if (match.support_level === 'partial') { badge = 'teilw.'; } else if (match.support_level === 'with_addon') { badge = 'Zusatz'; } else { badge = 'nein'; } const note = match.partial_reason ? `
${escapeHtml(match.partial_reason)}
` : (match.extra_info ? `
${escapeHtml(match.extra_info)}
` : ''); html += `${badge}${note}`; } }); tr.innerHTML = html; matBody.appendChild(tr); }); } function setTooltip(content, x, y) { if (!tooltipEl) return; tooltipEl.textContent = content; const padding = 12; const maxX = window.innerWidth - tooltipEl.offsetWidth - padding; const maxY = window.innerHeight - tooltipEl.offsetHeight - padding; const left = Math.min(x + 14, maxX); const top = Math.min(y + 14, maxY); tooltipEl.style.left = Math.max(left, padding) + 'px'; tooltipEl.style.top = Math.max(top, padding) + 'px'; tooltipEl.classList.add('is-visible'); tooltipEl.setAttribute('aria-hidden', 'false'); } function hideTooltip() { if (!tooltipEl) return; tooltipEl.classList.remove('is-visible'); tooltipEl.setAttribute('aria-hidden', 'true'); } function buildDetailsHtml(details) { return `
Tg °C${escapeHtml(details.tg)}
Düse${escapeHtml(details.nozzle)}
Platte${escapeHtml(details.plate)}
Zusatz${escapeHtml(details.extra)}
Emission${escapeHtml(details.emission)}
`; } function closeActiveDetails() { const open = matBody.querySelector('tr.mm-details-row'); if (open) { open.remove(); } const active = matBody.querySelector('tr.is-active'); if (active) { active.classList.remove('is-active'); } } matBody.addEventListener('mouseover', (e) => { const cell = e.target.closest('[data-details]'); if (!cell) return; let details; try { details = JSON.parse(cell.dataset.details || '{}'); } catch { details = {}; } const hoverText = `Tg °C: ${details.tg ?? '–'} | Düse: ${details.nozzle ?? '–'} | Platte: ${details.plate ?? '–'} | Zusatz: ${details.extra ?? '–'} | Emission: ${details.emission ?? '–'}`; setTooltip(hoverText, e.clientX, e.clientY); }); matBody.addEventListener('mousemove', (e) => { if (!tooltipEl || tooltipEl.getAttribute('aria-hidden') === 'true') return; setTooltip(tooltipEl.textContent, e.clientX, e.clientY); }); matBody.addEventListener('mouseout', (e) => { if (e.target.closest('[data-details]')) { hideTooltip(); } }); matBody.addEventListener('click', (e) => { if (e.target.closest('.mm-details-close')) { closeActiveDetails(); return; } const cell = e.target.closest('[data-details]'); if (!cell) return; const row = cell.closest('tr'); const isActive = row.classList.contains('is-active'); closeActiveDetails(); if (isActive) return; let details; try { details = JSON.parse(cell.dataset.details || '{}'); } catch { details = {}; } const colCount = row.children.length; const detailRow = document.createElement('tr'); detailRow.className = 'mm-details-row'; detailRow.innerHTML = `
${buildDetailsHtml(details)}
`; row.classList.add('is-active'); row.parentNode.insertBefore(detailRow, row.nextSibling); }); printerSelect.addEventListener('change', e => { const id = e.target.value; if (id) { loadSinglePrinter(id); printerCompare.selectedIndex = -1; } }); printerCompare.addEventListener('change', e => { const ids = Array.from(e.target.selectedOptions).map(o => o.value); if (ids.length) { loadMultiplePrinters(ids); } }); loadPrinters(); })();