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:
@@ -1,8 +1,4 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
|
||||||
Schadenprotokoll API - Flask Backend
|
|
||||||
Endpunkte: /generate, /analyze, /vorbericht, /templates
|
|
||||||
"""
|
|
||||||
from flask import Flask, request, jsonify, send_file
|
from flask import Flask, request, jsonify, send_file
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -12,126 +8,92 @@ from processors import parse_laufzettel, fill_pdf, analyze_pdf, generate_vorberi
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
TEMPLATES_DIR = "/opt/stacks/spa-hosting/html/schadenprotokoll/templates"
|
TEMPLATES_DIR = '/opt/stacks/spa-hosting/html/schadenprotokoll/templates'
|
||||||
TEMPLATE_PDF = os.path.join(TEMPLATES_DIR, "protokoll.pdf")
|
|
||||||
TEMPLATE_DOCX = os.path.join(TEMPLATES_DIR, "vorbericht.docx")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/health", methods=["GET"])
|
@app.route('/health', methods=['GET'])
|
||||||
def health():
|
def health():
|
||||||
return jsonify({"status": "ok"})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/templates", methods=["GET"])
|
@app.route('/generate', methods=['POST'])
|
||||||
def list_templates():
|
|
||||||
"""Listet alle verfuegbaren Templates"""
|
|
||||||
templates = []
|
|
||||||
for f in os.listdir(TEMPLATES_DIR):
|
|
||||||
path = os.path.join(TEMPLATES_DIR, f)
|
|
||||||
if os.path.isfile(path) and not f.endswith(".bak"):
|
|
||||||
templates.append({
|
|
||||||
"name": f,
|
|
||||||
"size": os.path.getsize(path),
|
|
||||||
"type": "pdf" if f.endswith(".pdf") else "docx"
|
|
||||||
})
|
|
||||||
return jsonify({"templates": templates})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/templates/upload", methods=["POST"])
|
|
||||||
def upload_template():
|
|
||||||
"""Laedt ein neues Template hoch"""
|
|
||||||
if "file" not in request.files:
|
|
||||||
return jsonify({"error": "Keine Datei hochgeladen"}), 400
|
|
||||||
|
|
||||||
file = request.files["file"]
|
|
||||||
ttype = request.form.get("type", "pdf")
|
|
||||||
|
|
||||||
if ttype == "pdf" and not file.filename.endswith(".pdf"):
|
|
||||||
return jsonify({"error": "PDF-Template muss .pdf sein"}), 400
|
|
||||||
if ttype == "docx" and not file.filename.endswith(".docx"):
|
|
||||||
return jsonify({"error": "Word-Template muss .docx sein"}), 400
|
|
||||||
|
|
||||||
target = TEMPLATE_PDF if ttype == "pdf" else TEMPLATE_DOCX
|
|
||||||
backup = target + ".bak"
|
|
||||||
|
|
||||||
if os.path.exists(target):
|
|
||||||
if os.path.exists(backup):
|
|
||||||
os.remove(backup)
|
|
||||||
os.rename(target, backup)
|
|
||||||
|
|
||||||
file.save(target)
|
|
||||||
return jsonify({"success": True, "message": f"Template erfolgreich hochgeladen"})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/generate", methods=["POST"])
|
|
||||||
def generate():
|
def generate():
|
||||||
"""Laufzettel.docx -> vorausgefuelltes Protokoll.pdf"""
|
# Akzeptiert template (PDF) und laufzettel (DOCX)
|
||||||
if "file" not in request.files:
|
if 'template' not in request.files or 'laufzettel' not in request.files:
|
||||||
return jsonify({"error": "Keine Datei hochgeladen"}), 400
|
return jsonify({'error': 'template und laufzettel erforderlich'}), 400
|
||||||
|
|
||||||
file = request.files["file"]
|
template_file = request.files['template']
|
||||||
if not file.filename.endswith(".docx"):
|
laufzettel_file = request.files['laufzettel']
|
||||||
return jsonify({"error": "Nur .docx Dateien erlaubt"}), 400
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp_in:
|
if not template_file.filename.endswith('.pdf'):
|
||||||
file.save(tmp_in.name)
|
return jsonify({'error': 'Template muss PDF sein'}), 400
|
||||||
|
if not laufzettel_file.filename.endswith('.docx'):
|
||||||
|
return jsonify({'error': 'Laufzettel muss DOCX sein'}), 400
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_tpl:
|
||||||
|
template_file.save(tmp_tpl.name)
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_lz:
|
||||||
|
laufzettel_file.save(tmp_lz.name)
|
||||||
try:
|
try:
|
||||||
data = parse_laufzettel(tmp_in.name)
|
data = parse_laufzettel(tmp_lz.name)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_out:
|
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_out:
|
||||||
fill_pdf(TEMPLATE_PDF, tmp_out.name, data)
|
fill_pdf(tmp_tpl.name, tmp_out.name, data)
|
||||||
os.unlink(tmp_in.name)
|
os.unlink(tmp_tpl.name)
|
||||||
return send_file(tmp_out.name, mimetype="application/pdf",
|
os.unlink(tmp_lz.name)
|
||||||
as_attachment=True, download_name="Schadenprotokoll_vorbefuellt.pdf")
|
return send_file(tmp_out.name, mimetype='application/pdf',
|
||||||
|
as_attachment=True, download_name='Schadenprotokoll_vorbefuellt.pdf')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
os.unlink(tmp_in.name)
|
os.unlink(tmp_tpl.name)
|
||||||
return jsonify({"error": str(e)}), 500
|
os.unlink(tmp_lz.name)
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route("/analyze", methods=["POST"])
|
@app.route('/analyze', methods=['POST'])
|
||||||
def analyze():
|
def analyze():
|
||||||
"""Ausgefuelltes PDF -> JSON mit allen Feldern"""
|
if 'file' not in request.files:
|
||||||
if "file" not in request.files:
|
return jsonify({'error': 'Keine Datei'}), 400
|
||||||
return jsonify({"error": "Keine Datei hochgeladen"}), 400
|
file = request.files['file']
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp:
|
||||||
file = request.files["file"]
|
|
||||||
if not file.filename.endswith(".pdf"):
|
|
||||||
return jsonify({"error": "Nur .pdf Dateien erlaubt"}), 400
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
|
|
||||||
file.save(tmp.name)
|
file.save(tmp.name)
|
||||||
try:
|
try:
|
||||||
result = analyze_pdf(tmp.name)
|
result = analyze_pdf(tmp.name)
|
||||||
os.unlink(tmp.name)
|
os.unlink(tmp.name)
|
||||||
return jsonify({"success": True, "data": result})
|
return jsonify({'success': True, 'data': result})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
os.unlink(tmp.name)
|
os.unlink(tmp.name)
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route("/vorbericht", methods=["POST"])
|
@app.route('/vorbericht', methods=['POST'])
|
||||||
def vorbericht():
|
def vorbericht():
|
||||||
"""Ausgefuelltes PDF -> Vorbericht.docx"""
|
if 'file' not in request.files:
|
||||||
if "file" not in request.files:
|
return jsonify({'error': 'Keine Datei'}), 400
|
||||||
return jsonify({"error": "Keine Datei hochgeladen"}), 400
|
file = request.files['file']
|
||||||
|
template_docx = os.path.join(TEMPLATES_DIR, 'vorbericht.docx')
|
||||||
file = request.files["file"]
|
with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as tmp_in:
|
||||||
if not file.filename.endswith(".pdf"):
|
|
||||||
return jsonify({"error": "Nur .pdf Dateien erlaubt"}), 400
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp_in:
|
|
||||||
file.save(tmp_in.name)
|
file.save(tmp_in.name)
|
||||||
try:
|
try:
|
||||||
pdf_data = analyze_pdf(tmp_in.name)
|
pdf_data = analyze_pdf(tmp_in.name)
|
||||||
with tempfile.NamedTemporaryFile(suffix=".docx", delete=False) as tmp_out:
|
with tempfile.NamedTemporaryFile(suffix='.docx', delete=False) as tmp_out:
|
||||||
generate_vorbericht(pdf_data, TEMPLATE_DOCX, tmp_out.name)
|
generate_vorbericht(pdf_data, template_docx, tmp_out.name)
|
||||||
os.unlink(tmp_in.name)
|
os.unlink(tmp_in.name)
|
||||||
return send_file(tmp_out.name,
|
return send_file(tmp_out.name,
|
||||||
mimetype="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
mimetype='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||||
as_attachment=True, download_name="Vorbericht.docx")
|
as_attachment=True, download_name='Vorbericht.docx')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
os.unlink(tmp_in.name)
|
os.unlink(tmp_in.name)
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
@app.route('/templates', methods=['GET'])
|
||||||
app.run(host="0.0.0.0", port=5050, debug=False)
|
def list_templates():
|
||||||
|
templates = []
|
||||||
|
for f in os.listdir(TEMPLATES_DIR):
|
||||||
|
if not f.endswith('.bak'):
|
||||||
|
path = os.path.join(TEMPLATES_DIR, f)
|
||||||
|
templates.append({'name': f, 'size': os.path.getsize(path)})
|
||||||
|
return jsonify({'templates': templates})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5050, debug=False)
|
||||||
|
|||||||
@@ -1,171 +1,142 @@
|
|||||||
// Tab Navigation
|
document.querySelectorAll('.tab').forEach(tab => {
|
||||||
document.querySelectorAll(".tab").forEach(tab => {
|
tab.addEventListener('click', () => {
|
||||||
tab.addEventListener("click", () => {
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||||
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||||
document.querySelectorAll(".tab-content").forEach(c => c.classList.remove("active"));
|
tab.classList.add('active');
|
||||||
tab.classList.add("active");
|
document.getElementById(tab.dataset.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 API_BASE = '/schadenprotokoll/api';
|
||||||
const zone = document.getElementById(zoneId);
|
let templateFile = null;
|
||||||
const input = document.getElementById(inputId);
|
let laufzettelFile = null;
|
||||||
const info = document.getElementById(infoId);
|
|
||||||
const btn = document.getElementById(btnId);
|
|
||||||
if (!zone || !input) return;
|
|
||||||
|
|
||||||
zone.addEventListener("click", () => input.click());
|
function showStatus(id, type, msg) {
|
||||||
zone.addEventListener("dragover", (e) => { e.preventDefault(); zone.classList.add("dragover"); });
|
const el = document.getElementById(id);
|
||||||
zone.addEventListener("dragleave", () => zone.classList.remove("dragover"));
|
if (el) { el.className = 'status visible ' + type; el.textContent = msg; }
|
||||||
zone.addEventListener("drop", (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
zone.classList.remove("dragover");
|
|
||||||
if (e.dataTransfer.files.length) {
|
|
||||||
input.files = e.dataTransfer.files;
|
|
||||||
updateFileInfo(input, info, btn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(); }
|
||||||
});
|
});
|
||||||
input.addEventListener("change", () => updateFileInfo(input, info, btn));
|
templateInput.addEventListener('change', handleTemplateSelect);
|
||||||
}
|
|
||||||
|
|
||||||
function updateFileInfo(input, info, btn) {
|
function handleTemplateSelect() {
|
||||||
if (input.files.length && info && btn) {
|
if (templateInput.files.length) {
|
||||||
const file = input.files[0];
|
templateFile = templateInput.files[0];
|
||||||
info.innerHTML = "<strong>OK " + file.name + "</strong> (" + (file.size/1024).toFixed(1) + " KB)";
|
document.getElementById('template-info').innerHTML = '<strong>OK: ' + templateFile.name + '</strong>';
|
||||||
info.classList.add("visible");
|
document.getElementById('template-info').classList.add('visible');
|
||||||
btn.disabled = false;
|
checkGenerateReady();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showStatus(id, type, message) {
|
const laufzettelZone = document.getElementById('laufzettel-zone');
|
||||||
const status = document.getElementById(id);
|
const laufzettelInput = document.getElementById('laufzettel-input');
|
||||||
if (status) {
|
laufzettelZone.addEventListener('click', () => laufzettelInput.click());
|
||||||
status.className = "status visible " + type;
|
laufzettelZone.addEventListener('dragover', e => { e.preventDefault(); laufzettelZone.classList.add('dragover'); });
|
||||||
status.textContent = message;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_BASE = "/schadenprotokoll/api";
|
document.getElementById('generate-btn').addEventListener('click', async () => {
|
||||||
|
showStatus('generate-status', 'loading', 'Verarbeite...');
|
||||||
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();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append('template', templateFile);
|
||||||
|
formData.append('laufzettel', laufzettelFile);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(API_BASE + "/generate", { method: "POST", body: formData });
|
const response = await fetch(API_BASE + '/generate', { method: 'POST', body: formData });
|
||||||
if (!response.ok) throw new Error((await response.json()).error);
|
if (!response.ok) throw new Error((await response.json()).error);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const a = document.createElement("a");
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = "Schadenprotokoll_vorbefuellt.pdf";
|
a.download = 'Schadenprotokoll_vorbefuellt.pdf';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||||
a.click();
|
showStatus('generate-status', 'success', 'PDF erfolgreich generiert!');
|
||||||
document.body.removeChild(a);
|
|
||||||
showStatus("generate-status", "success", "PDF erfolgreich generiert!");
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showStatus("generate-status", "error", "Fehler: " + err.message);
|
showStatus('generate-status', 'error', 'Fehler: ' + err.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("analyze-btn").addEventListener("click", async () => {
|
function setupSimpleUpload(zoneId, inputId, infoId, btnId) {
|
||||||
showStatus("analyze-status", "loading", "Analysiere PDF...");
|
const zone = document.getElementById(zoneId);
|
||||||
const file = document.getElementById("pdf-input").files[0];
|
const input = document.getElementById(inputId);
|
||||||
const formData = new FormData();
|
if (!zone || !input) return;
|
||||||
formData.append("file", file);
|
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 {
|
try {
|
||||||
const response = await fetch(API_BASE + "/analyze", { method: "POST", body: formData });
|
const response = await fetch(API_BASE + '/analyze', { method: 'POST', body: formData });
|
||||||
if (!response.ok) throw new Error((await response.json()).error);
|
if (!response.ok) throw new Error((await response.json()).error);
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
displayAnalysisResult(result.data);
|
let html = '';
|
||||||
showStatus("analyze-status", "success", "Analyse abgeschlossen");
|
for (const [k, v] of Object.entries(result.data.textfields || {})) {
|
||||||
} catch (err) {
|
if (v) html += '<div class="dropdown-item"><strong>' + k + ':</strong> ' + v + '</div>';
|
||||||
showStatus("analyze-status", "error", "Fehler: " + err.message);
|
|
||||||
}
|
}
|
||||||
|
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); }
|
||||||
});
|
});
|
||||||
|
|
||||||
function displayAnalysisResult(data) {
|
document.getElementById('vorbericht-btn').addEventListener('click', async () => {
|
||||||
const container = document.getElementById("analyze-data");
|
showStatus('vorbericht-status', 'loading', 'Erstelle Vorbericht...');
|
||||||
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();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append('file', document.getElementById('vorbericht-input').files[0]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(API_BASE + "/vorbericht", { method: "POST", body: formData });
|
const response = await fetch(API_BASE + '/vorbericht', { method: 'POST', body: formData });
|
||||||
if (!response.ok) throw new Error((await response.json()).error);
|
if (!response.ok) throw new Error((await response.json()).error);
|
||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const a = document.createElement("a");
|
const a = document.createElement('a');
|
||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = "Vorbericht.docx";
|
a.download = 'Vorbericht.docx';
|
||||||
document.body.appendChild(a);
|
document.body.appendChild(a); a.click(); document.body.removeChild(a);
|
||||||
a.click();
|
showStatus('vorbericht-status', 'success', 'Vorbericht erstellt!');
|
||||||
document.body.removeChild(a);
|
} catch (err) { showStatus('vorbericht-status', 'error', 'Fehler: ' + err.message); }
|
||||||
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");
|
|
||||||
|
|||||||
@@ -17,23 +17,33 @@
|
|||||||
<button class="tab active" data-tab="generate">1. Generieren</button>
|
<button class="tab active" data-tab="generate">1. Generieren</button>
|
||||||
<button class="tab" data-tab="analyze">2. Auswerten</button>
|
<button class="tab" data-tab="analyze">2. Auswerten</button>
|
||||||
<button class="tab" data-tab="vorbericht">3. Vorbericht</button>
|
<button class="tab" data-tab="vorbericht">3. Vorbericht</button>
|
||||||
<button class="tab" data-tab="admin">⚙️ Templates</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 1: Generieren -->
|
<!-- Tab 1: Generieren -->
|
||||||
<div id="generate" class="tab-content active">
|
<div id="generate" class="tab-content active">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h4>Schadenlaufzettel → Protokoll PDF</h4>
|
<h4>Schadenlaufzettel + PDF Template = Ausgefuelltes Protokoll</h4>
|
||||||
<p>Laedt Daten aus dem Schadenlaufzettel.docx und fuellt das Schadenprotokoll-PDF vor.</p>
|
<p>Waehle beide Dateien aus und klicke auf Generieren.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="upload-row">
|
||||||
|
<div class="upload-zone" id="template-zone">
|
||||||
|
<input type="file" id="template-input" accept=".pdf">
|
||||||
|
<h3>1. PDF Template</h3>
|
||||||
|
<p>Schadenprotokoll-Vorlage.pdf</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="upload-zone" id="laufzettel-zone">
|
<div class="upload-zone" id="laufzettel-zone">
|
||||||
<input type="file" id="laufzettel-input" accept=".docx">
|
<input type="file" id="laufzettel-input" accept=".docx">
|
||||||
<h3>📄 Schadenlaufzettel.docx hochladen</h3>
|
<h3>2. Laufzettel</h3>
|
||||||
<p>Datei hierher ziehen oder klicken</p>
|
<p>Schadenlaufzettel.docx</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="file-info" id="template-info"></div>
|
||||||
<div class="file-info" id="laufzettel-info"></div>
|
<div class="file-info" id="laufzettel-info"></div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-primary" id="generate-btn" disabled>⚙️ PDF generieren</button>
|
<button class="btn btn-primary" id="generate-btn" disabled>PDF generieren</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status" id="generate-status"></div>
|
<div class="status" id="generate-status"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,16 +52,16 @@
|
|||||||
<div id="analyze" class="tab-content">
|
<div id="analyze" class="tab-content">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h4>Ausgefuelltes PDF analysieren</h4>
|
<h4>Ausgefuelltes PDF analysieren</h4>
|
||||||
<p>Liest alle Formularfelder und Dropdown-Auswahlen aus dem Schadenprotokoll.</p>
|
<p>Liest alle Formularfelder und Dropdown-Auswahlen aus.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="upload-zone" id="pdf-zone">
|
<div class="upload-zone" id="pdf-zone">
|
||||||
<input type="file" id="pdf-input" accept=".pdf">
|
<input type="file" id="pdf-input" accept=".pdf">
|
||||||
<h3>📋 Schadenprotokoll.pdf hochladen</h3>
|
<h3>Schadenprotokoll.pdf</h3>
|
||||||
<p>Das ausgefuellte PDF-Formular</p>
|
<p>Das ausgefuellte PDF-Formular</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-info" id="pdf-info"></div>
|
<div class="file-info" id="pdf-info"></div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-primary" id="analyze-btn" disabled>🔍 Analysieren</button>
|
<button class="btn btn-primary" id="analyze-btn" disabled>Analysieren</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status" id="analyze-status"></div>
|
<div class="status" id="analyze-status"></div>
|
||||||
<div class="result" id="analyze-result">
|
<div class="result" id="analyze-result">
|
||||||
@@ -63,53 +73,23 @@
|
|||||||
<!-- Tab 3: Vorbericht -->
|
<!-- Tab 3: Vorbericht -->
|
||||||
<div id="vorbericht" class="tab-content">
|
<div id="vorbericht" class="tab-content">
|
||||||
<div class="info-box">
|
<div class="info-box">
|
||||||
<h4>Protokoll → Vorbericht Word</h4>
|
<h4>Protokoll zu Vorbericht Word</h4>
|
||||||
<p>Generiert einen strukturierten Vorbericht aus dem ausgefuellten Schadenprotokoll.</p>
|
<p>Generiert einen strukturierten Vorbericht aus dem ausgefuellten PDF.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="upload-zone" id="vorbericht-zone">
|
<div class="upload-zone" id="vorbericht-zone">
|
||||||
<input type="file" id="vorbericht-input" accept=".pdf">
|
<input type="file" id="vorbericht-input" accept=".pdf">
|
||||||
<h3>📋 Ausgefuelltes Schadenprotokoll.pdf</h3>
|
<h3>Ausgefuelltes Schadenprotokoll.pdf</h3>
|
||||||
<p>Das vollstaendig ausgefuellte PDF-Formular</p>
|
<p>Das vollstaendig ausgefuellte PDF</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="file-info" id="vorbericht-info"></div>
|
<div class="file-info" id="vorbericht-info"></div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn btn-primary" id="vorbericht-btn" disabled>📝 Vorbericht erstellen</button>
|
<button class="btn btn-primary" id="vorbericht-btn" disabled>Vorbericht erstellen</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="status" id="vorbericht-status"></div>
|
<div class="status" id="vorbericht-status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 4: Admin/Templates -->
|
|
||||||
<div id="admin" class="tab-content">
|
|
||||||
<div class="info-box">
|
|
||||||
<h4>Template-Verwaltung</h4>
|
|
||||||
<p>Hier kannst du die PDF- und Word-Templates hochladen, die fuer die Generierung verwendet werden.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="template-list" id="template-list">
|
|
||||||
<h4>Aktuelle Templates:</h4>
|
|
||||||
<div id="templates-container">Lade...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="template-upload">
|
|
||||||
<h4>Neues Template hochladen:</h4>
|
|
||||||
<div class="upload-row">
|
|
||||||
<div class="upload-zone small" id="pdf-template-zone">
|
|
||||||
<input type="file" id="pdf-template-input" accept=".pdf">
|
|
||||||
<h3>📄 PDF Template</h3>
|
|
||||||
<p>Schadenprotokoll-Vorlage</p>
|
|
||||||
</div>
|
|
||||||
<div class="upload-zone small" id="docx-template-zone">
|
|
||||||
<input type="file" id="docx-template-input" accept=".docx">
|
|
||||||
<h3>📝 Word Template</h3>
|
|
||||||
<p>Vorbericht-Vorlage</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="status" id="admin-status"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<p>Schadenprotokoll Tool v1.0 | artetui.de</p>
|
<p>Schadenprotokoll Tool v1.1 | artetui.de</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user