{% macro app_pill(source_app) -%} {% if source_app == 'tools' %} WK {% elif source_app == 'management' %} LG {% else %} {{ source_app }} {% endif %} {%- endmacro %} {% macro item_type_badge(t) -%} {%- set cls = { 'tool': 'badge-blue', 'measuring_equipment': 'badge-purple', 'clamping': 'badge-blue', 'material': 'badge-emerald', 'consumable': 'badge-amber', 'packaging': 'badge-neutral', 'other': 'badge-neutral' } -%} {{ item_type_label(t) }} {%- endmacro %} {% macro action_badge(code, name) -%} {%- set cls = { 'rent': 'badge-blue', 'return': 'badge-emerald', 'order': 'badge-amber', 'receive': 'badge-emerald', 'stock_in': 'badge-emerald', 'stock_out': 'badge-amber', 'rebook': 'badge-neutral', 'inventory': 'badge-neutral', 'scrap': 'badge-red', } -%} {{ name or code }} {%- endmacro %} {% macro order_status_badge(s) -%} {%- set cls = {'open': 'badge-amber', 'received': 'badge-emerald', 'closed': 'badge-neutral', 'cancelled': 'badge-red'} -%} {{ order_status_label(s) }} {%- endmacro %} {% macro avatar(first_name, last_name, photo=None) -%} {%- set initials = (first_name[0] if first_name else '?') ~ (last_name[0] if last_name else '') -%} {%- if photo -%} {{ first_name }} {{ last_name }} {%- else -%} {{ initials }} {%- endif -%} {%- endmacro %} {% macro money(value) -%} {%- if value is none -%}—{%- else -%} {{ "{:,.2f}".format(value).replace(",", "X").replace(".", ",").replace("X", ".") }} € {%- endif -%} {%- endmacro %} {# v1.8.0: PDF-Sprach-Picker (DE/EN/FR) für Beleg-Detailseiten. entity = "quotes"|"sales-orders"|"invoices"|"delivery-notes" eid = numerische ID current_lang = aktuell auf dem Beleg gespeicherte Sprache (default 'de') #} {% macro pdf_lang_picker(entity, eid, current_lang="de") -%} {%- set cl = (current_lang or 'de')[:2] -%}
PDF-Sprache
{%- endmacro %} {# ========== Form helpers ========== #} {% macro field(label, name, value="", type="text", required=false, hint=None, placeholder=None, attrs="") -%}
{% if hint %}

{{ hint }}

{% endif %}
{%- endmacro %} {% macro select_field(label, name, options, current="", required=false, allow_empty=true, empty_label="—") -%}
{%- endmacro %} {% macro checkbox(label, name, checked=false) -%} {%- endmacro %} {# Attachments card: lists existing files + upload form. action_url is the POST endpoint. #} {% macro attachments_card(attachments, action_url, title="Anhänge", subtitle=None, accept="image/*,.pdf,.svg") -%}

{{ title }}

{{ subtitle or "Bilder, Zeichnungen, PDF-Skizzen — max. 8 MB pro Datei" }}

{% if attachments %}
{% for a in attachments %}
{% set is_img = a.content_type and a.content_type.startswith('image/') %} {% if is_img %} {{ a.filename }} {% else %}

{{ a.filename.split('.')[-1].upper() }}

{% endif %}
{{ a.filename }}
{{ ((a.size_bytes or 0) / 1024) | round(0, 'common') | int }} KB
{% if a.first_name %}
— {{ a.first_name }} {{ a.last_name }}
{% endif %}
{% endfor %}
{% else %}
Noch keine Anhänge.
{% endif %}

PNG · JPG · PDF · SVG bis 8 MB

{%- endmacro %} {% macro number_field(label, name, value=0, min=None, hint=None) -%}
{% if hint %}

{{ hint }}

{% endif %}
{%- endmacro %} {# ============== EMPTY-STATE — onboarding-freundliche Hinweise ============== Verwendung: {% from "_macros.html" import empty_state %} {{ empty_state( icon="📥", title="Noch keine Anfragen", description="Hier landen alle Mail-Anfragen die per IMAP reinkommen, plus alles was du manuell anlegst.", workflow="📥 Anfrage → 💼 Angebot → 📋 Auftrag → 🏭 Werkstatt → 📦 Versand → 🧾 Rechnung", ctas=[ {"label": "+ Erste Anfrage anlegen", "href": "/inquiries/new", "primary": True}, {"label": "📥 IMAP konfigurieren", "href": "/settings#imap"}, {"label": "📚 Wie funktioniert das?", "href": "/handbuch/de/pdf", "external": True}, ], ) }} ========================================================================== #} {% macro empty_state(icon='📭', title='Noch keine Daten', description='', workflow=None, ctas=None) -%}
{{ icon }}

{{ title }}

{% if description %}

{{ description }}

{% endif %} {% if workflow %}

{{ workflow }}

{% endif %} {% if ctas %}
{% for c in ctas %} {{ c.label }} {% endfor %}
{% endif %}
{%- endmacro %} {# v1.12.0: Inline-Sparkline-Widget Usage: {{ sparkline_widget("/api/sparkline/customer/" ~ customer.id, "Umsatz 12 Mo", "revenue") }} #} {%- macro sparkline_widget(api_url, label, value_key="revenue", suffix="€") -%}
{{ label }}
{%- endmacro %} {# v1.12.0 — Generic-Entity-Detail-Macros Mini-Version statt 8-Std-Refactor aller 30 Templates. Neue Detail-Templates können ab v1.13 diese Macros nutzen statt von Grund auf neu zu schreiben. Usage-Beispiel: {{ entity_header(item.ident, item.description, icon="📦", badges=[...]) }} {{ entity_tabs("/items/" ~ item.id, tabs, current_tab) }} {{ entity_quick_actions(actions) }} #} {%- macro entity_header(title, subtitle="", icon="", badges=[]) -%}
{% if icon %}
{{ icon }}
{% endif %}

{{ title }}

{% if subtitle %}

{{ subtitle }}

{% endif %} {% if badges %}
{% for b in badges %} {{ b.label }} {% endfor %}
{% endif %}
{%- endmacro %} {%- macro entity_tabs(base_url, tabs, current_tab) -%} {# tabs = [{"key": "stammdaten", "label": "📋 Stammdaten"}, ...] #}
{% for t in tabs %} {{ t.label }} {% endfor %}
{%- endmacro %} {%- macro entity_quick_actions(actions) -%} {# actions = [{"label": "+ Neu", "url": "/quotes/new", "primary": true}, ...] #}
{% for a in actions %} {{ a.label }} {% endfor %}
{%- endmacro %} {%- macro entity_meta_row(items) -%} {# items = [{"label": "Erstellt", "value": "2026-05-17"}, ...] #}
{% for i in items %}
{{ i.label }}
{{ i.value or '—' }}
{% endfor %}
{%- endmacro %}