/* Baustelle PWA Hauptlogik. Alle Routen + Views in einer Datei. */ const main = () => document.getElementById('main'); const title = (s) => document.getElementById('page-title').textContent = s; window.showToast = function (msg, kind) { const t = document.createElement('div'); t.className = 'toast' + (kind ? ' ' + kind : ''); t.textContent = msg; document.getElementById('toast-container').appendChild(t); setTimeout(() => t.remove(), 2800); }; function showLoader(text) { main().innerHTML = '
' + (text || 'Lade…') + '
'; } function setNav(visible, active) { const nav = document.getElementById('bottom-nav'); nav.style.display = visible ? '' : 'none'; nav.querySelectorAll('button').forEach(b => { b.classList.toggle('active', b.dataset.route === active); }); } function setBack(visible, hash) { const btn = document.getElementById('back-btn'); btn.style.display = visible ? '' : 'none'; btn.onclick = () => { if (hash) router.go(hash); else history.back(); }; } /* ----- Auth-Check ----- */ async function ensureAuth() { const t = await api.getToken(); if (!t) { router.go('#/login'); return false; } return true; } /* ====== ROUTES ====== */ router.on('/login', async () => { title('Anmelden'); setNav(false); setBack(false); main().innerHTML = `

🔧 Baustelle

`; document.getElementById('login-form').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(e.target); try { await api.login(fd.get('login'), fd.get('password')); showToast('Erfolgreich angemeldet'); router.go('#/orders'); } catch (err) { showToast(err.message, 'error'); } }); }); router.on('/orders', async () => { if (!(await ensureAuth())) return; title('Aufträge'); setNav(true, 'orders'); setBack(false); showLoader('Lade Aufträge…'); try { const data = await api.listOrders({ open: 1 }); if (!data.orders.length) { main().innerHTML = '
📭
Keine offenen Aufträge
'; return; } const html = `
${renderOrderList(data.orders)}
`; main().innerHTML = html; document.querySelectorAll('.order-card').forEach(c => { c.addEventListener('click', () => router.go('#/orders/' + c.dataset.id)); }); document.getElementById('order-search').addEventListener('input', async (e) => { const q = e.target.value; const d = await api.listOrders({ q, open: 1 }); document.getElementById('order-list').innerHTML = renderOrderList(d.orders); document.querySelectorAll('.order-card').forEach(c => { c.addEventListener('click', () => router.go('#/orders/' + c.dataset.id)); }); }); } catch (e) { main().innerHTML = '
⚠️
' + e.message + '
'; } }); function renderOrderList(orders) { return orders.map(o => `
${escapeHtml(o.ref)}
${escapeHtml(o.customer.name || '')}
${escapeHtml((o.customer.zip || '') + ' ' + (o.customer.town || ''))} ${o.bericht_count > 0 ? `📑 ${o.bericht_count}` : ''}
`).join(''); } router.on('/orders/:id', async (args) => { if (!(await ensureAuth())) return; setNav(true, 'orders'); setBack(true, '#/orders'); showLoader('Lade Auftrag…'); try { const data = await api.getOrder(args.id); const photos = await api.listOrderPhotos(args.id).catch(() => ({ photos: [] })); title(data.order.ref); main().innerHTML = `

Kunde

${escapeHtml(data.customer.name)}

${escapeHtml(data.customer.address || '')}

${escapeHtml((data.customer.zip || '') + ' ' + (data.customer.town || ''))}

${data.customer.phone ? `

📞 ${escapeHtml(data.customer.phone)}

` : ''}
${data.order.auftragsbeschreibung ? `

Beschreibung

${escapeHtml(data.order.auftragsbeschreibung)}

` : ''}

Hochgeladene Fotos (${photos.photos.length})

${photos.photos.map(p => `
`).join('')}
`; loadThumbs(); const camInput = document.getElementById('camera-input'); const galInput = document.getElementById('gallery-input'); document.getElementById('btn-take-photo').onclick = () => camInput.click(); document.getElementById('btn-pick-photo').onclick = () => galInput.click(); async function handleFiles(files) { for (const f of files) { await uploadPhoto(args.id, f); } // Reload Photo-Liste try { const np = await api.listOrderPhotos(args.id); document.getElementById('photo-grid').innerHTML = np.photos.map(p => `
`).join(''); loadThumbs(); } catch (e) {} } camInput.addEventListener('change', () => handleFiles(camInput.files)); galInput.addEventListener('change', () => handleFiles(galInput.files)); } catch (e) { main().innerHTML = '
⚠️
' + e.message + '
'; } }); async function uploadPhoto(orderId, file) { showToast('Optimiere & sende ' + file.name); const blob = await resizeImage(file, 2000); if (!navigator.onLine) { await offline.enqueuePhoto(orderId, blob, file.name); showToast('Offline — Foto in Queue', 'warn'); return; } try { await api.uploadOrderPhoto(orderId, blob, file.name); showToast('✓ ' + file.name + ' hochgeladen'); } catch (e) { await offline.enqueuePhoto(orderId, blob, file.name); showToast('Upload fehlgeschlagen — in Queue', 'error'); } } async function resizeImage(file, maxSide) { return new Promise((resolve) => { const img = new Image(); const url = URL.createObjectURL(file); img.onload = () => { URL.revokeObjectURL(url); const scale = Math.min(1, maxSide / Math.max(img.width, img.height)); if (scale === 1) { resolve(file); return; } const c = document.createElement('canvas'); c.width = Math.round(img.width * scale); c.height = Math.round(img.height * scale); c.getContext('2d').drawImage(img, 0, 0, c.width, c.height); c.toBlob(b => resolve(b || file), 'image/jpeg', 0.85); }; img.onerror = () => { URL.revokeObjectURL(url); resolve(file); }; img.src = url; }); } /** * Lädt alle sichtbaren Thumbnails im aktuellen Photo-Grid. * Nutzt /api/photo.php mit JWT (Header kann nicht schicken, * deshalb Blob-URLs). */ async function loadThumbs() { const thumbs = document.querySelectorAll('.photo-grid .thumb[data-relpath]'); for (const t of thumbs) { const rel = t.dataset.relpath; try { // Erst Thumbnail versuchen (_small), bei Misserfolg das Original let url = await api.getPhotoBlobUrl(rel, 'small'); if (!url) url = await api.getPhotoBlobUrl(rel); if (url) { t.innerHTML = ''; } else { t.innerHTML = '
'; } } catch (e) { t.innerHTML = '
'; } } } router.on('/reports', async () => { if (!(await ensureAuth())) return; title('Berichte'); setNav(true, 'reports'); setBack(false); main().innerHTML = '
📑
Berichte-Liste folgt
'; }); router.on('/settings', async () => { if (!(await ensureAuth())) return; title('Einstellungen'); setNav(true, 'settings'); setBack(false); const user = await idb.get('user') || {}; main().innerHTML = `

Konto

${escapeHtml(user.name || user.login || 'Unbekannt')}

${escapeHtml(user.login || '')}

Baustelle PWA v1.0

`; document.getElementById('btn-sync').onclick = async () => { await offline.syncQueue(); showToast('Sync ausgelöst'); }; document.getElementById('btn-logout').onclick = async () => { await api.logout(); router.go('#/login'); }; }); /* Bottom nav */ document.addEventListener('DOMContentLoaded', () => { document.querySelectorAll('#bottom-nav button').forEach(b => { b.addEventListener('click', () => router.go('#/' + b.dataset.route)); }); }); function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }