diff --git a/app.css b/app.css
index 8268f6f..774e159 100644
--- a/app.css
+++ b/app.css
@@ -405,3 +405,36 @@ body {
width: 100px;
flex-shrink: 0;
}
+
+/* Audio-Items in Auftrags-Detail */
+.audio-list { display: flex; flex-direction: column; gap: 8px; }
+.audio-item {
+ background: #2a2a30;
+ border-radius: 6px;
+ padding: 10px 12px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: wrap;
+}
+.audio-item .audio-name {
+ flex: 1;
+ font-size: 13px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.audio-item .audio-play {
+ background: #337ab7;
+ color: #fff;
+ border: none;
+ border-radius: 50%;
+ width: 36px;
+ height: 36px;
+ font-size: 14px;
+ cursor: pointer;
+ flex-shrink: 0;
+}
+.audio-item audio { width: 100%; flex-basis: 100%; }
+
+.btn[disabled] { opacity: 0.5; cursor: not-allowed; }
diff --git a/app.js b/app.js
index acb5267..a3f3d43 100644
--- a/app.js
+++ b/app.js
@@ -124,9 +124,13 @@ router.on('/orders/:id', async (args) => {
const photos = await api.listOrderPhotos(args.id).catch(() => ({ photos: [] }));
title(data.order.ref);
- // Nur Bilder in der Foto-Grid zeigen; alles andere (PDFs etc.) unten auflisten
+ // Nach MIME-Type aufteilen
const imagePhotos = photos.photos.filter(p => (p.mime || '').startsWith('image/'));
- const otherDocs = photos.photos.filter(p => !(p.mime || '').startsWith('image/'));
+ const audioFiles = photos.photos.filter(p => (p.mime || '').startsWith('audio/') || /\.(webm|mp3|ogg|m4a|wav)$/i.test(p.filename));
+ const otherDocs = photos.photos.filter(p =>
+ !(p.mime || '').startsWith('image/') &&
+ !(p.mime || '').startsWith('audio/') &&
+ !/\.(webm|mp3|ogg|m4a|wav)$/i.test(p.filename));
main().innerHTML = `
@@ -155,6 +159,14 @@ router.on('/orders/:id', async (args) => {
${imagePhotos.map(p => `
`).join('')}
+ ${audioFiles.length ? `
+
+
🎙 Sprachnotizen (${audioFiles.length})
+
+ ${audioFiles.map(a => `
${escapeHtml(a.filename)}
`).join('')}
+
+
` : ''}
+
${otherDocs.length ? `
Weitere Dokumente (${otherDocs.length})
@@ -170,6 +182,44 @@ router.on('/orders/:id', async (args) => {
// Sprachnotiz
document.getElementById('btn-voice').onclick = () => openVoiceModal(args.id);
+ // Audio-Files abspielen
+ document.querySelectorAll('.audio-item .audio-play').forEach(btn => {
+ btn.addEventListener('click', async (e) => {
+ const item = e.target.closest('.audio-item');
+ const rel = item.dataset.relpath;
+ const mime = item.dataset.mime || 'audio/webm';
+ // Existierenden Player toggeln
+ let player = item.querySelector('audio');
+ if (player) {
+ if (player.paused) player.play();
+ else player.pause();
+ return;
+ }
+ btn.textContent = '⏳';
+ try {
+ const t = await api.getToken();
+ const params = new URLSearchParams({ relpath: rel, jwt: t });
+ const r = await fetch(window.location.origin + '/custom/bericht/api/photo.php?' + params.toString());
+ if (!r.ok) throw new Error('Load failed');
+ const blob = await r.blob();
+ const url = URL.createObjectURL(new Blob([blob], { type: mime }));
+ player = document.createElement('audio');
+ player.controls = true;
+ player.src = url;
+ player.style.width = '100%';
+ player.style.marginTop = '8px';
+ item.appendChild(player);
+ player.play();
+ btn.textContent = '⏸';
+ player.onplay = () => btn.textContent = '⏸';
+ player.onpause = () => btn.textContent = '▶';
+ } catch (err) {
+ showToast('Audio laden fehlgeschlagen: ' + err.message, 'error');
+ btn.textContent = '▶';
+ }
+ });
+ });
+
loadThumbs();
const camInput = document.getElementById('camera-input');
@@ -302,7 +352,8 @@ router.on('/reports/:id', async (args) => {
title(data.report.ref);
const statusLabel = data.report.status === 1 ? 'Final' : 'Entwurf';
- const canFinalize = data.report.status !== 1 && data.pages.length > 0;
+ const hasPages = data.pages.length > 0;
+ const finalizeLabel = data.report.status === 1 ? '🔄 PDF neu erzeugen' : '📑 Bericht finalisieren (PDF)';
main().innerHTML = `
@@ -314,19 +365,24 @@ router.on('/reports/:id', async (args) => {
Status: ${statusLabel}
+ ${hasPages ? `
${data.pages.map(p => `
`).join('')}
-
+
` : '📭
Dieser Bericht hat noch keine Seiten. Fotos aufnehmen, um Seiten hinzuzufügen.
'}
- ${canFinalize ? `` : ''}
+
`;
loadThumbs();
- if (canFinalize) {
- document.getElementById('btn-finalize').onclick = async () => {
- if (!confirm('Bericht jetzt finalisieren und PDF erzeugen?')) return;
+ const finalizeBtn = document.getElementById('btn-finalize');
+ if (finalizeBtn) {
+ finalizeBtn.onclick = async () => {
+ if (!hasPages) { showToast('Bericht hat keine Seiten', 'warn'); return; }
+ if (!confirm(data.report.status === 1
+ ? 'PDF neu erzeugen und unter den verknüpften Dokumenten ablegen?'
+ : 'Bericht jetzt finalisieren und PDF erzeugen?')) return;
showToast('PDF wird erzeugt…');
try {
const r = await api.finalizeReport(args.id);
diff --git a/sw.js b/sw.js
index 071e390..9c7e7f8 100644
--- a/sw.js
+++ b/sw.js
@@ -4,7 +4,7 @@
* - API-Calls: network-first, kein offline-cache (da auth-pflichtig)
*/
-const CACHE = 'baustelle-v2';
+const CACHE = 'baustelle-v3';
const SHELL = [
'./',
'./index.html',