Files
signage/templates/customer.html
Erik Thiele 64db742d31 feat: Logo-Upload mit automatischem Freistellen
Findet die automatische Suche kein Logo, kann pro Kunde eine Bilddatei
hochgeladen werden. Ein Upload hat Vorrang vor der Suche.

Freistellen:
- remove_logo_background() entfernt den Hintergrund per Flood Fill von
  den Bildraendern her. Bewusst kein globaler Farbvergleich - sonst
  verschwindet auch Weiss innerhalb des Logos (etwa Text in einer
  Flaeche). Weiche Kante gegen Treppeneffekte.
- Umgesetzt mit Pillow, das ueber qrcode[pil] ohnehin vorhanden ist.
  Bewusst kein ML-Modell wie rembg: der Container laeuft mit read-only
  Root-FS, ein Modell-Cache zur Laufzeit waere dort nicht moeglich.
- Bereits transparente Bilder bleiben unangetastet. Ist der Bildrand
  nicht einfarbig, wird nicht freigestellt und die Oberflaeche weist
  darauf hin, statt ein beschaedigtes Logo zu liefern.
- Checkbox "Hintergrund behalten" schaltet das Freistellen pro Kunde ab.
- Gespeichert wird immer als PNG (JPEG kann keine Transparenz), grosse
  Bilder werden auf 800px verkleinert.

Ablage unter media/<site>/logos/logo_<slot>.png, neue Route
/media/<site>/logos/<datei>. Die URL traegt einen Zeitstempel, damit
Player kein altes Logo aus dem Cache zeigen.

Nebenbei behoben: welcome_data speicherte nur die erfolgreichen Kunden
statt der Felder 1-3. Fiel Kunde 1 aus, rutschte Kunde 2 beim naechsten
Aufruf in Feld 1 und Logo wie Ansprechpartner verschoben sich mit. Jetzt
feldweise - dadurch bleibt auch ein hochgeladenes Logo beim erneuten
Generieren erhalten, solange der Kundenname im selben Feld steht.

Die drei Kundenbloecke im Formular sind zu einer Schleife
zusammengefasst, damit die Upload-Felder nicht dreifach gepflegt werden
muessen.

README, AGENTS.md und Hilfeseite nachgezogen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-02 13:55:40 +02:00

252 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Willkommensseite</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<!-- ─── Dark Mode: Theme vor dem Rendern setzen ─── -->
<script>
(() => {
const stored = window.localStorage.getItem("signage-theme");
const theme = stored === "dark" || stored === "light" ? stored : "light";
document.documentElement.setAttribute("data-bs-theme", theme);
})();
</script>
<link rel="stylesheet"
href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
<link rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
<script defer
src="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/js/tabler.min.js"></script>
{% include "_styles.html" %}
<style>
.form-label { font-weight: 600; }
.card { box-shadow: 0 20px 60px rgba(0,0,0,0.08); }
/* ─── Logo-Vorschau-Box (gestrichelte Border) ─── */
.logo-preview-box {
border: 2px dashed var(--ccm-border);
border-radius: 0.75rem;
min-height: 120px;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: #ffffff;
}
[data-bs-theme="dark"] .logo-preview-box {
background: #1a1e24;
}
.logo-preview-box img {
max-height: 100px;
max-width: 100%;
object-fit: contain;
background: #ffffff;
padding: 8px;
border-radius: 0.5rem;
}
.logo-preview-box .placeholder {
color: var(--ccm-muted);
font-size: 0.85rem;
text-align: center;
}
</style>
</head>
<body>
<div class="page">
{% set brand_subtitle = "Willkommensseite" %}
{% set site_dropdown_url_prefix = "/willkommen?site=" %}
{% include "_header.html" %}
<!-- ═══════════════════════════════════════════════════
Navigation (zweite Zeile)
═══════════════════════════════════════════════════ -->
<div class="navbar-expand-md">
<div class="collapse navbar-collapse show">
<div class="navbar navbar-dark nav-surface">
<div class="container-xl">
<div class="d-flex flex-wrap gap-2 py-2">
<a class="btn btn-white" href="/admin/{{ current_site }}"><i class="ti ti-layout-dashboard me-1"></i>Übersicht</a>
<a class="btn btn-white" href="/admin/{{ current_site }}/priority"><i class="ti ti-alert-triangle me-1"></i>Priority Playlist</a>
<a class="btn btn-white active" href="/willkommen?site={{ current_site }}"><i class="ti ti-file-plus me-1"></i>Willkommensseite</a>
</div>
</div>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════════════
CONTENT – Willkommensseite-Formular
Bis zu 3 Kundennamen eingeben → Logos via OpenAI + Brandfetch
→ welcome.html wird generiert und in den Playlists aller Screens
mit "welcome_enabled" vorne platziert
═══════════════════════════════════════════════════ -->
<div class="page-wrapper">
<div class="container-xl mt-4">
<div class="card mb-4">
<div class="card-header">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">Willkommensseite</div>
<h2 class="page-title">Kundeninformationen eingeben (max. 3)</h2>
</div>
</div>
</div>
<div class="card-body">
{% if error %}
<div class="alert alert-danger">{{ error }}</div>
{% endif %}
{% if success %}
<div class="alert alert-success">{{ success }}</div>
{% endif %}
{% if logo_hinweise %}
<div class="alert alert-warning">
<strong>Hinweise zu den hochgeladenen Logos:</strong>
<ul class="mb-0">
{% for h in logo_hinweise %}<li>{{ h }}</li>{% endfor %}
</ul>
</div>
{% endif %}
<!-- ─── Logo-Vorschau (wird nach POST angezeigt) ─── -->
<div class="row g-3 mb-4">
{% for i in range(3) %}
<div class="col-md-4">
<label class="form-label">Logo {{ i + 1 }}</label>
<div class="logo-preview-box">
{% if logo_urls and logo_urls[i] %}
<img src="{{ logo_urls[i] }}" alt="Logo {{ i + 1 }}">
{% else %}
<div class="placeholder">kein Logo</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
<!-- ─── Eingabeformular (bis zu 3 Kunden) ─── -->
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="site" value="{{ site }}">
{# Echte Zeilenumbrueche statt &#10; - in einer Variablen wuerde Jinja
das kaufmaennische Und escapen und die Entity waere als Text sichtbar. #}
{% set slots = [
("z. B. Beispiel GmbH", "**Max Mustermann**\n+49 123 4567890\n*E-Mail*: max@beispiel.de"),
("z. B. Muster AG", "**Maria Schmidt**\n+49 456 7890123\n*E-Mail*: maria@muster.de"),
("z. B. Firma GmbH", "**Sabine Weber**\n+49 789 0123456\n*E-Mail*: sabine@firma.de")
] %}
<div class="row g-3 mb-3">
{% for i in range(3) %}
<div class="col-md-4">
<label class="form-label" for="customer_name_{{ i + 1 }}">Kunde {{ i + 1 }}{% if i > 0 %} (optional){% endif %}</label>
<input type="text" class="form-control" id="customer_name_{{ i + 1 }}"
name="customer_name_{{ i + 1 }}" placeholder="{{ slots[i][0] }}"
value="{{ customer_names[i] if customer_names else '' }}">
<label class="form-label mt-2" for="contact_{{ i + 1 }}">Ansprechpartner (optional)</label>
<textarea class="form-control" id="contact_{{ i + 1 }}" name="contact_{{ i + 1 }}" rows="4"
placeholder="{{ slots[i][1] }}">{{ contacts[i] if contacts else '' }}</textarea>
<label class="form-label mt-2" for="logo_file_{{ i + 1 }}">Logo hochladen (optional)</label>
<input type="file" class="form-control" id="logo_file_{{ i + 1 }}"
name="logo_file_{{ i + 1 }}" accept="image/*">
<div class="form-check mt-1">
<input class="form-check-input" type="checkbox"
id="logo_keep_bg_{{ i + 1 }}" name="logo_keep_bg_{{ i + 1 }}">
<label class="form-check-label small" for="logo_keep_bg_{{ i + 1 }}">Hintergrund behalten</label>
</div>
</div>
{% endfor %}
</div>
<div class="text-muted small mb-3">
<i class="ti ti-info-circle me-1"></i>Ein hochgeladenes Logo hat Vorrang vor der automatischen Suche.
Der Hintergrund wird automatisch freigestellt, damit das Logo auf der Willkommensseite nicht in einem
weissen Kasten steht. Am besten funktioniert das bei einem Logo auf einfarbigem Grund; ein PNG mit
bereits transparentem Hintergrund wird unverändert übernommen.
</div>
<hr class="mt-4 mb-3">
<div class="mb-3">
<label class="form-label" for="valid_until">Anzeigen bis <span class="text-muted">(optional)</span></label>
<input type="date" class="form-control" id="valid_until" name="valid_until"
value="{{ request.form.get('valid_until', '') }}" style="max-width: 260px;">
<div class="form-hint">Ohne Angabe bleibt die Willkommensseite stehen, bis sie von Hand entfernt wird. Mit Datum verschwindet sie nach diesem Tag auf allen Screens von selbst.</div>
</div>
<div class="mb-3 text-muted">
<p class="mb-1">Das System sucht automatisch nach den Kundenlogos</p>
<p class="mb-0">und erstellt eine angepasste Willkommensseite.</p>
<p class="mb-1">Sie erscheint auf allen Screens dieses Standorts, bei denen im Tab „Einstellungen" die Option <em>Willkommensseite hier anzeigen</em> aktiv ist{% if welcome_screens %} – aktuell: <strong>{{ welcome_screens | join(', ') }}</strong>{% else %} – <strong>aktuell keiner</strong>{% endif %}.</p>
<p class="mb-0 small"><i class="ti ti-markdown me-1"></i>Ansprechpartner unterstützen <strong>fett</strong>, <em>kursiv</em>, <code>Code</code>, Aufzählungen (<code>- </code>) sowie Zeilenumbrüche.</p>
</div>
<button type="submit" class="btn btn-primary">
<i class="ti ti-file-plus me-1"></i>Logos &amp; Willkommensseite generieren
</button>
<a href="?reset=1&amp;site={{ site }}" class="btn btn-outline-secondary ms-2">
<i class="ti ti-trash me-1"></i>Zurücksetzen
</a>
</form>
</div>
</div>
<!-- ═══════════════════════════════════════════════════
Karte: Hintergrundbild hochladen (pro Standort)
═══════════════════════════════════════════════════ -->
<div class="card mb-4">
<div class="card-header">
<div class="row align-items-center">
<div class="col">
<div class="page-pretitle">Willkommensseite</div>
<h2 class="page-title">Hintergrund</h2>
</div>
</div>
</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Aktuelles Hintergrundbild</label>
<div class="logo-preview-box" style="min-height:200px; background: #f0f0f0;">
<img src="{{ background_url or '/static/wallpaper.png' }}" alt="Hintergrund" style="max-height:180px; width:100%; object-fit:contain; background:transparent; padding:0;">
</div>
</div>
<div class="col-md-6">
<label class="form-label">Neues Hintergrundbild hochladen</label>
<form method="POST" action="/admin/{{ site }}/upload-background" enctype="multipart/form-data">
<input type="file" class="form-control mb-2" name="background" accept=".jpg,.jpeg,.png" required>
<button type="submit" class="btn btn-primary">
<i class="ti ti-upload me-1"></i>Hochladen
</button>
</form>
{% if background_url %}
<form method="POST" action="/admin/{{ site }}/delete-background" class="mt-2">
<button type="submit" class="btn btn-outline-danger">
<i class="ti ti-trash me-1"></i>Zurücksetzen auf Standard
</button>
</form>
{% endif %}
<p class="text-muted mt-2 mb-0 small">
<i class="ti ti-info-circle me-1"></i>
Wenn kein Hintergrundbild hochgeladen ist, wird das Standard-Wallpaper verwendet.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{% include "_footer.html" %}
</div> <!-- /page -->
</body>
</html>