diff --git a/app.css b/app.css index 97a8196..52222e4 100644 --- a/app.css +++ b/app.css @@ -436,6 +436,29 @@ body { flex-shrink: 0; } .audio-item audio { width: 100%; flex-basis: 100%; } +.audio-item .audio-transcribe { + background: #2a2a30; + color: #7aa2f7; + border: 1px solid #444; + border-radius: 50%; + width: 36px; + height: 36px; + font-size: 14px; + cursor: pointer; + flex-shrink: 0; +} +.audio-transcript { + flex-basis: 100%; + background: #1a1a1f; + padding: 10px 12px; + margin-top: 6px; + border-radius: 4px; + border-left: 3px solid #7aa2f7; + font-size: 13px; + line-height: 1.5; + color: #e0e0e0; + white-space: pre-wrap; +} .btn[disabled] { opacity: 0.5; cursor: not-allowed; } diff --git a/app.js b/app.js index ffdebfc..59b7322 100644 --- a/app.js +++ b/app.js @@ -253,6 +253,7 @@ router.on('/orders/:id', async (args) => { +

Hochgeladene Fotos (${imagePhotos.length})

@@ -264,7 +265,11 @@ router.on('/orders/:id', async (args) => {

🎙 Sprachnotizen (${audioFiles.length})

- ${audioFiles.map(a => `
${escapeHtml(a.filename)}
`).join('')} + ${audioFiles.map(a => `
+ ${escapeHtml(a.filename)} + + +
`).join('')}
` : ''} @@ -283,6 +288,33 @@ router.on('/orders/:id', async (args) => { // Sprachnotiz document.getElementById('btn-voice').onclick = () => openVoiceModal(args.id); + // Neuen Bericht anlegen + document.getElementById('btn-new-report').onclick = () => openNewReportModal(args.id); + + // Transkribieren + document.querySelectorAll('.audio-item .audio-transcribe').forEach(btn => { + btn.addEventListener('click', async (e) => { + const item = e.target.closest('.audio-item'); + const rel = item.dataset.relpath; + btn.textContent = '⏳'; + try { + const r = await api.transcribeAudio(rel); + const text = r.text || '(leer)'; + const existing = item.querySelector('.audio-transcript'); + if (existing) existing.remove(); + const box = document.createElement('div'); + box.className = 'audio-transcript'; + box.textContent = text; + item.appendChild(box); + btn.textContent = '📝'; + showToast('✓ Transkribiert'); + } catch (err) { + btn.textContent = '📝'; + showToast('Whisper-Fehler: ' + err.message, 'error'); + } + }); + }); + // Audio-Files abspielen document.querySelectorAll('.audio-item .audio-play').forEach(btn => { btn.addEventListener('click', async (e) => { @@ -814,6 +846,96 @@ function bindReportPageInteractions(reportId) { }); } +/* ============================================================ + * NEW REPORT MODAL (Schnell-Bericht mit Meta + Vorlage + ODT) + * ============================================================ */ +async function openNewReportModal(orderId) { + // Templates parallel laden + let tpls = [], odts = []; + try { tpls = (await api.listTemplates()).templates || []; } catch (e) {} + try { odts = (await api.listOdtTemplates()).templates || []; } catch (e) {} + + const modal = document.createElement('div'); + modal.className = 'fullscreen-modal'; + modal.innerHTML = ` +
+ +
📑 Neuer Bericht
+ +
+
+ + + + ${tpls.length ? ` + + ` : ''} + + +
+ + +
+ + ${odts.length ? ` + + ` : ''} +
+ `; + document.body.appendChild(modal); + + // ODT-Default vorauswählen + try { + const odtData = await api.listOdtTemplates(); + if (odtData.default) { + const sel = modal.querySelector('#nr-odt'); + if (sel) sel.value = odtData.default; + } + } catch (e) {} + + modal.querySelector('#nr-close').onclick = () => modal.remove(); + modal.querySelector('#nr-save').onclick = async () => { + const titel = modal.querySelector('#nr-titel').value.trim(); + const tplSel = modal.querySelector('#nr-template'); + const tpl = tplSel ? parseInt(tplSel.value, 10) : 0; + const format = modal.querySelector('#nr-format').value; + const orient = modal.querySelector('#nr-orient').value; + const odtSel = modal.querySelector('#nr-odt'); + const odt = odtSel ? odtSel.value : ''; + showToast('Lege Bericht an…'); + try { + const res = await api.createReport({ + element_type: 'order', + element_id: parseInt(orderId, 10), + titel: titel, + template_id: tpl, + page_format: format, + page_orientation: orient, + template_odt: odt, + }); + showToast('✓ Bericht angelegt'); + modal.remove(); + router.go('#/reports/' + res.bericht_id); + } catch (e) { + showToast('Fehler: ' + e.message, 'error'); + } + }; +} + /* ============================================================ * SEITEN-AKTIONEN MODAL (Notiz, Löschen, Vollbild) * ============================================================ */ diff --git a/lib/api.js b/lib/api.js index 795c97d..3521a8b 100644 --- a/lib/api.js +++ b/lib/api.js @@ -99,6 +99,21 @@ return request('/reports.php'); } + async function createReport(opts) { + return request('/reports.php?action=create', { + method: 'POST', + body: JSON.stringify(opts), + }); + } + + async function listTemplates() { + return request('/templates.php'); + } + + async function listOdtTemplates() { + return request('/odt_templates.php'); + } + async function finalizeReport(id) { return request('/reports.php?id=' + id + '&action=finalize', { method: 'POST' }); } @@ -116,6 +131,13 @@ return request('/voice.php?order_id=' + orderId, { method: 'POST', body: fd }); } + async function transcribeAudio(relpath) { + return request('/transcribe.php', { + method: 'POST', + body: JSON.stringify({ relpath }), + }); + } + async function uploadAnnotatedPhoto(orderId, fileBlob, filename) { // Wie uploadOrderPhoto — Skizze ist schon ins Bild eingebrannt return uploadOrderPhoto(orderId, fileBlob, filename); @@ -204,8 +226,8 @@ login, logout, listOrders, getOrder, listOrderPhotos, uploadOrderPhoto, listCustomers, getCustomer, - getReport, listReports, finalizeReport, - deletePhoto, uploadVoiceNote, uploadAnnotatedPhoto, + getReport, listReports, createReport, listTemplates, listOdtTemplates, finalizeReport, + deletePhoto, uploadVoiceNote, transcribeAudio, uploadAnnotatedPhoto, getPhotoBlobUrl, clearPhotoCache, deletePage, updatePageNote, uploadSignature, getPdfBlobUrl, reorderPages, };