Allow user to select PDF template before generating

- Tab 1 now has two upload zones: PDF Template + Laufzettel DOCX
- API accepts both files in /generate endpoint
- Removed hardcoded template path

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-21 12:05:25 +00:00
parent f0f0eef9c0
commit 3e27052cc1
4 changed files with 231 additions and 318 deletions

View File

@@ -1,171 +1,142 @@
// Tab Navigation
document.querySelectorAll(".tab").forEach(tab => {
tab.addEventListener("click", () => {
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
document.querySelectorAll(".tab-content").forEach(c => c.classList.remove("active"));
tab.classList.add("active");
document.getElementById(tab.dataset.tab).classList.add("active");
if (tab.dataset.tab === "admin") loadTemplates();
});
});
function setupUploadZone(zoneId, inputId, infoId, btnId) {
const zone = document.getElementById(zoneId);
const input = document.getElementById(inputId);
const info = document.getElementById(infoId);
const btn = document.getElementById(btnId);
if (!zone || !input) return;
zone.addEventListener("click", () => input.click());
zone.addEventListener("dragover", (e) => { e.preventDefault(); zone.classList.add("dragover"); });
zone.addEventListener("dragleave", () => zone.classList.remove("dragover"));
zone.addEventListener("drop", (e) => {
e.preventDefault();
zone.classList.remove("dragover");
if (e.dataTransfer.files.length) {
input.files = e.dataTransfer.files;
updateFileInfo(input, info, btn);
}
});
input.addEventListener("change", () => updateFileInfo(input, info, btn));
}
function updateFileInfo(input, info, btn) {
if (input.files.length && info && btn) {
const file = input.files[0];
info.innerHTML = "<strong>OK " + file.name + "</strong> (" + (file.size/1024).toFixed(1) + " KB)";
info.classList.add("visible");
btn.disabled = false;
}
}
function showStatus(id, type, message) {
const status = document.getElementById(id);
if (status) {
status.className = "status visible " + type;
status.textContent = message;
}
}
const API_BASE = "/schadenprotokoll/api";
setupUploadZone("laufzettel-zone", "laufzettel-input", "laufzettel-info", "generate-btn");
setupUploadZone("pdf-zone", "pdf-input", "pdf-info", "analyze-btn");
setupUploadZone("vorbericht-zone", "vorbericht-input", "vorbericht-info", "vorbericht-btn");
document.getElementById("generate-btn").addEventListener("click", async () => {
showStatus("generate-status", "loading", "Verarbeite Laufzettel...");
const file = document.getElementById("laufzettel-input").files[0];
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch(API_BASE + "/generate", { method: "POST", body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const blob = await response.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "Schadenprotokoll_vorbefuellt.pdf";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
showStatus("generate-status", "success", "PDF erfolgreich generiert!");
} catch (err) {
showStatus("generate-status", "error", "Fehler: " + err.message);
}
});
document.getElementById("analyze-btn").addEventListener("click", async () => {
showStatus("analyze-status", "loading", "Analysiere PDF...");
const file = document.getElementById("pdf-input").files[0];
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch(API_BASE + "/analyze", { method: "POST", body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const result = await response.json();
displayAnalysisResult(result.data);
showStatus("analyze-status", "success", "Analyse abgeschlossen");
} catch (err) {
showStatus("analyze-status", "error", "Fehler: " + err.message);
}
});
function displayAnalysisResult(data) {
const container = document.getElementById("analyze-data");
let html = "";
for (const [key, value] of Object.entries(data.textfields || {})) {
if (value) html += "<div class='dropdown-item'><strong>" + key + ":</strong> " + value + "</div>";
}
for (const [key, val] of Object.entries(data.dropdowns || {})) {
if (val.selected) html += "<div class='dropdown-item'><strong>" + key + ":</strong> " + val.selected + "</div>";
}
container.innerHTML = html || "<p>Keine Felder gefunden.</p>";
document.getElementById("analyze-result").classList.add("visible");
}
document.getElementById("vorbericht-btn").addEventListener("click", async () => {
showStatus("vorbericht-status", "loading", "Erstelle Vorbericht...");
const file = document.getElementById("vorbericht-input").files[0];
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch(API_BASE + "/vorbericht", { method: "POST", body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const blob = await response.blob();
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "Vorbericht.docx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
showStatus("vorbericht-status", "success", "Vorbericht erstellt!");
} catch (err) {
showStatus("vorbericht-status", "error", "Fehler: " + err.message);
}
});
function setupTemplateUpload(zoneId, inputId, templateType) {
const zone = document.getElementById(zoneId);
const input = document.getElementById(inputId);
if (!zone || !input) return;
zone.addEventListener("click", () => input.click());
zone.addEventListener("dragover", (e) => { e.preventDefault(); zone.classList.add("dragover"); });
zone.addEventListener("dragleave", () => zone.classList.remove("dragover"));
zone.addEventListener("drop", (e) => {
e.preventDefault();
zone.classList.remove("dragover");
if (e.dataTransfer.files.length) uploadTemplate(e.dataTransfer.files[0], templateType);
});
input.addEventListener("change", () => { if (input.files.length) uploadTemplate(input.files[0], templateType); });
}
async function loadTemplates() {
const container = document.getElementById("templates-container");
if (!container) return;
try {
const response = await fetch(API_BASE + "/templates");
const data = await response.json();
container.innerHTML = data.templates.map(t =>
"<div class='template-item'><span>" + t.name + "</span><span>" + (t.size/1024).toFixed(0) + " KB</span></div>"
).join("");
} catch (err) { container.innerHTML = "Fehler"; }
}
async function uploadTemplate(file, type) {
showStatus("admin-status", "loading", "Lade hoch...");
const formData = new FormData();
formData.append("file", file);
formData.append("type", type);
try {
const response = await fetch(API_BASE + "/templates/upload", { method: "POST", body: formData });
if (!response.ok) throw new Error((await response.json()).error);
showStatus("admin-status", "success", "Hochgeladen!");
loadTemplates();
} catch (err) { showStatus("admin-status", "error", "Fehler: " + err.message); }
}
setupTemplateUpload("pdf-template-zone", "pdf-template-input", "pdf");
setupTemplateUpload("docx-template-zone", "docx-template-input", "docx");
document.querySelectorAll('.tab').forEach(tab => {
tab.addEventListener('click', () => {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
});
});
const API_BASE = '/schadenprotokoll/api';
let templateFile = null;
let laufzettelFile = null;
function showStatus(id, type, msg) {
const el = document.getElementById(id);
if (el) { el.className = 'status visible ' + type; el.textContent = msg; }
}
function checkGenerateReady() {
document.getElementById('generate-btn').disabled = !(templateFile && laufzettelFile);
}
const templateZone = document.getElementById('template-zone');
const templateInput = document.getElementById('template-input');
templateZone.addEventListener('click', () => templateInput.click());
templateZone.addEventListener('dragover', e => { e.preventDefault(); templateZone.classList.add('dragover'); });
templateZone.addEventListener('dragleave', () => templateZone.classList.remove('dragover'));
templateZone.addEventListener('drop', e => {
e.preventDefault(); templateZone.classList.remove('dragover');
if (e.dataTransfer.files.length) { templateInput.files = e.dataTransfer.files; handleTemplateSelect(); }
});
templateInput.addEventListener('change', handleTemplateSelect);
function handleTemplateSelect() {
if (templateInput.files.length) {
templateFile = templateInput.files[0];
document.getElementById('template-info').innerHTML = '<strong>OK: ' + templateFile.name + '</strong>';
document.getElementById('template-info').classList.add('visible');
checkGenerateReady();
}
}
const laufzettelZone = document.getElementById('laufzettel-zone');
const laufzettelInput = document.getElementById('laufzettel-input');
laufzettelZone.addEventListener('click', () => laufzettelInput.click());
laufzettelZone.addEventListener('dragover', e => { e.preventDefault(); laufzettelZone.classList.add('dragover'); });
laufzettelZone.addEventListener('dragleave', () => laufzettelZone.classList.remove('dragover'));
laufzettelZone.addEventListener('drop', e => {
e.preventDefault(); laufzettelZone.classList.remove('dragover');
if (e.dataTransfer.files.length) { laufzettelInput.files = e.dataTransfer.files; handleLaufzettelSelect(); }
});
laufzettelInput.addEventListener('change', handleLaufzettelSelect);
function handleLaufzettelSelect() {
if (laufzettelInput.files.length) {
laufzettelFile = laufzettelInput.files[0];
document.getElementById('laufzettel-info').innerHTML = '<strong>OK: ' + laufzettelFile.name + '</strong>';
document.getElementById('laufzettel-info').classList.add('visible');
checkGenerateReady();
}
}
document.getElementById('generate-btn').addEventListener('click', async () => {
showStatus('generate-status', 'loading', 'Verarbeite...');
const formData = new FormData();
formData.append('template', templateFile);
formData.append('laufzettel', laufzettelFile);
try {
const response = await fetch(API_BASE + '/generate', { method: 'POST', body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'Schadenprotokoll_vorbefuellt.pdf';
document.body.appendChild(a); a.click(); document.body.removeChild(a);
showStatus('generate-status', 'success', 'PDF erfolgreich generiert!');
} catch (err) {
showStatus('generate-status', 'error', 'Fehler: ' + err.message);
}
});
function setupSimpleUpload(zoneId, inputId, infoId, btnId) {
const zone = document.getElementById(zoneId);
const input = document.getElementById(inputId);
if (!zone || !input) return;
zone.addEventListener('click', () => input.click());
zone.addEventListener('dragover', e => { e.preventDefault(); zone.classList.add('dragover'); });
zone.addEventListener('dragleave', () => zone.classList.remove('dragover'));
zone.addEventListener('drop', e => {
e.preventDefault(); zone.classList.remove('dragover');
if (e.dataTransfer.files.length) { input.files = e.dataTransfer.files; updateInfo(); }
});
input.addEventListener('change', updateInfo);
function updateInfo() {
if (input.files.length) {
document.getElementById(infoId).innerHTML = '<strong>OK: ' + input.files[0].name + '</strong>';
document.getElementById(infoId).classList.add('visible');
document.getElementById(btnId).disabled = false;
}
}
}
setupSimpleUpload('pdf-zone', 'pdf-input', 'pdf-info', 'analyze-btn');
setupSimpleUpload('vorbericht-zone', 'vorbericht-input', 'vorbericht-info', 'vorbericht-btn');
document.getElementById('analyze-btn').addEventListener('click', async () => {
showStatus('analyze-status', 'loading', 'Analysiere...');
const formData = new FormData();
formData.append('file', document.getElementById('pdf-input').files[0]);
try {
const response = await fetch(API_BASE + '/analyze', { method: 'POST', body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const result = await response.json();
let html = '';
for (const [k, v] of Object.entries(result.data.textfields || {})) {
if (v) html += '<div class="dropdown-item"><strong>' + k + ':</strong> ' + v + '</div>';
}
for (const [k, d] of Object.entries(result.data.dropdowns || {})) {
if (d.selected) html += '<div class="dropdown-item"><strong>' + k + ':</strong> ' + d.selected + '</div>';
}
document.getElementById('analyze-data').innerHTML = html || 'Keine Daten';
document.getElementById('analyze-result').classList.add('visible');
showStatus('analyze-status', 'success', 'Fertig');
} catch (err) { showStatus('analyze-status', 'error', 'Fehler: ' + err.message); }
});
document.getElementById('vorbericht-btn').addEventListener('click', async () => {
showStatus('vorbericht-status', 'loading', 'Erstelle Vorbericht...');
const formData = new FormData();
formData.append('file', document.getElementById('vorbericht-input').files[0]);
try {
const response = await fetch(API_BASE + '/vorbericht', { method: 'POST', body: formData });
if (!response.ok) throw new Error((await response.json()).error);
const blob = await response.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'Vorbericht.docx';
document.body.appendChild(a); a.click(); document.body.removeChild(a);
showStatus('vorbericht-status', 'success', 'Vorbericht erstellt!');
} catch (err) { showStatus('vorbericht-status', 'error', 'Fehler: ' + err.message); }
});