Logo-Upload: Bild hochladen/loeschen via Settings, Einbindung in PDF mit Fallback auf Text
This commit is contained in:
43
app.py
43
app.py
@@ -13,7 +13,9 @@ from flask import Flask, render_template, request, jsonify, redirect, url_for, s
|
|||||||
from fpdf import FPDF
|
from fpdf import FPDF
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
|
||||||
CONFIG_FILE = 'druckkalkulation_config.yaml'
|
CONFIG_FILE = 'druckkalkulation_config.yaml'
|
||||||
|
LOGO_FILE = 'static/logo.png'
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
'projektName': '',
|
'projektName': '',
|
||||||
@@ -42,6 +44,7 @@ DEFAULT_VALUES = {
|
|||||||
'paypal': '',
|
'paypal': '',
|
||||||
'iban': '',
|
'iban': '',
|
||||||
'bankName': '',
|
'bankName': '',
|
||||||
|
'logo': '',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -163,6 +166,32 @@ def save_config_route():
|
|||||||
return jsonify({'status': 'ok'})
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/upload_logo', methods=['POST'])
|
||||||
|
def upload_logo():
|
||||||
|
"""Logo-Datei hochladen und in static/logo.png speichern."""
|
||||||
|
if 'logo' not in request.files:
|
||||||
|
return jsonify({'error': 'Keine Datei'}), 400
|
||||||
|
file = request.files['logo']
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({'error': 'Keine Datei ausgewaehlt'}), 400
|
||||||
|
file.save(LOGO_FILE)
|
||||||
|
config = load_config()
|
||||||
|
config['logo'] = 'logo.png'
|
||||||
|
save_config(config)
|
||||||
|
return jsonify({'status': 'ok', 'filename': 'logo.png'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/delete_logo', methods=['POST'])
|
||||||
|
def delete_logo():
|
||||||
|
"""Logo loeschen."""
|
||||||
|
if os.path.exists(LOGO_FILE):
|
||||||
|
os.remove(LOGO_FILE)
|
||||||
|
config = load_config()
|
||||||
|
config['logo'] = ''
|
||||||
|
save_config(config)
|
||||||
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
|
|
||||||
@app.route('/invoice')
|
@app.route('/invoice')
|
||||||
def invoice():
|
def invoice():
|
||||||
"""Rechnungsseite mit Kundendaten und Vorschau."""
|
"""Rechnungsseite mit Kundendaten und Vorschau."""
|
||||||
@@ -221,18 +250,16 @@ def invoice_pdf():
|
|||||||
pdf.set_x(left)
|
pdf.set_x(left)
|
||||||
pdf.cell(col_w, 4, " | ".join(contact_parts), ln=True)
|
pdf.cell(col_w, 4, " | ".join(contact_parts), ln=True)
|
||||||
|
|
||||||
# Logo rechts
|
# Logo rechts (Bild oder Text-Fallback)
|
||||||
|
if os.path.exists(LOGO_FILE):
|
||||||
|
pdf.image(LOGO_FILE, x=col_right, y=15, w=60)
|
||||||
|
else:
|
||||||
pdf.set_font("Arial", "B", 22)
|
pdf.set_font("Arial", "B", 22)
|
||||||
pdf.set_xy(col_right, 15)
|
pdf.set_xy(col_right, 15)
|
||||||
y_logo = pdf.get_y()
|
|
||||||
|
|
||||||
line1 = "JET"
|
|
||||||
line2 = "Design"
|
|
||||||
pdf.set_xy(col_right, y_logo)
|
|
||||||
pdf.set_text_color(6, 111, 209)
|
pdf.set_text_color(6, 111, 209)
|
||||||
pdf.cell(col_w, 8, line1, ln=True, align='R')
|
pdf.cell(col_w, 8, "JET", ln=True, align='R')
|
||||||
pdf.set_x(col_right)
|
pdf.set_x(col_right)
|
||||||
pdf.cell(col_w, 8, line2, ln=True, align='R')
|
pdf.cell(col_w, 8, "Design", ln=True, align='R')
|
||||||
pdf.set_text_color(0, 0, 0)
|
pdf.set_text_color(0, 0, 0)
|
||||||
|
|
||||||
# --- Rechnungsdaten ---
|
# --- Rechnungsdaten ---
|
||||||
|
|||||||
@@ -139,6 +139,41 @@ function showNotification(msg) {
|
|||||||
setTimeout(() => { container.style.display = 'none'; }, 2500);
|
setTimeout(() => { container.style.display = 'none'; }, 2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Logo hochladen via AJAX. */
|
||||||
|
async function uploadLogo() {
|
||||||
|
const input = document.getElementById('logo-upload');
|
||||||
|
if (!input.files || !input.files[0]) {
|
||||||
|
showNotification('Bitte eine Datei auswaehlen');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('logo', input.files[0]);
|
||||||
|
try {
|
||||||
|
const res = await fetch('/upload_logo', { method: 'POST', body: formData });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
showNotification('Logo gespeichert!');
|
||||||
|
setTimeout(() => location.reload(), 800);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showNotification('Fehler beim Hochladen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Logo loeschen. */
|
||||||
|
async function deleteLogo() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/delete_logo', { method: 'POST' });
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.status === 'ok') {
|
||||||
|
showNotification('Logo entfernt');
|
||||||
|
setTimeout(() => location.reload(), 800);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showNotification('Fehler beim Loeschen');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Initiale Berechnung beim Seitenladen. */
|
/** Initiale Berechnung beim Seitenladen. */
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
recalculate();
|
recalculate();
|
||||||
|
|||||||
@@ -78,6 +78,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header"><i class="ti ti-photo me-2"></i>Logo</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="mb-3">
|
||||||
|
{% if config.logo %}
|
||||||
|
<div class="mb-2">
|
||||||
|
<img src="{{ url_for('static', filename='logo.png') }}" style="max-height:80px;max-width:240px" class="border rounded p-1">
|
||||||
|
</div>
|
||||||
|
<button type="button" class="btn btn-outline-danger btn-sm mb-2" onclick="deleteLogo()">
|
||||||
|
<i class="ti ti-trash me-1"></i>Logo entfernen
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
<input type="file" class="form-control" id="logo-upload" accept="image/png,image/jpeg,image/webp">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm mt-2" onclick="uploadLogo()">
|
||||||
|
<i class="ti ti-upload me-1"></i>Hochladen
|
||||||
|
</button>
|
||||||
|
<div class="form-text">Empfohlen: PNG, max. 240px breit. Erscheint rechts oben auf der Rechnung.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header"><i class="ti ti-tools me-2"></i>Drucker Abschreibung</div>
|
<div class="card-header"><i class="ti ti-tools me-2"></i>Drucker Abschreibung</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
Reference in New Issue
Block a user