""" Template Helpers für MYP Platform Jinja2 Helper-Funktionen für UI-Komponenten """ from flask import current_app, url_for, request from markupsafe import Markup import json from datetime import datetime from typing import Dict, Any, Optional, List import calendar import random class UIHelpers: """UI-Helper-Klasse für Template-Funktionen""" @staticmethod def component_button(text: str, type: str = "primary", size: str = "md", classes: str = "", icon: str = "", onclick: str = "", disabled: bool = False, **attrs) -> Markup: """ Erstellt einen Button mit Tailwind-Klassen Args: text: Button-Text type: Button-Typ (primary, secondary, danger, success) size: Button-Größe (sm, md, lg) classes: Zusätzliche CSS-Klassen icon: SVG-Icon-Code onclick: JavaScript-Code für onclick disabled: Button deaktiviert **attrs: Zusätzliche HTML-Attribute """ base_classes = ["btn"] # Typ-spezifische Klassen type_classes = { "primary": "btn-primary", "secondary": "btn-secondary", "danger": "btn-danger", "success": "btn-success" } base_classes.append(type_classes.get(type, "btn-primary")) # Größen-spezifische Klassen size_classes = { "sm": "btn-sm", "md": "", "lg": "btn-lg" } if size_classes.get(size): base_classes.append(size_classes[size]) if disabled: base_classes.append("opacity-50 cursor-not-allowed") # Zusätzliche Klassen hinzufügen if classes: base_classes.append(classes) # HTML-Attribute aufbauen attrs_str = "" for key, value in attrs.items(): attrs_str += f' {key.replace("_", "-")}="{value}"' if onclick: attrs_str += f' onclick="{onclick}"' if disabled: attrs_str += ' disabled' # Icon und Text kombinieren content = "" if icon: content += f'{icon}' content += text html = f'''''' return Markup(html) @staticmethod def component_badge(text: str, type: str = "blue", classes: str = "") -> Markup: """ Erstellt ein Badge/Tag-Element Args: text: Badge-Text type: Badge-Typ (blue, green, red, yellow, purple) classes: Zusätzliche CSS-Klassen """ base_classes = ["badge", f"badge-{type}"] if classes: base_classes.append(classes) html = f'{text}' return Markup(html) @staticmethod def component_status_badge(status: str, type: str = "job") -> Markup: """ Erstellt ein Status-Badge für Jobs oder Drucker Args: status: Status-Wert type: Typ (job, printer) """ if type == "job": class_name = f"job-status job-{status}" else: class_name = f"printer-status printer-{status}" # Status-Text übersetzen translations = { "job": { "queued": "In Warteschlange", "printing": "Wird gedruckt", "completed": "Abgeschlossen", "failed": "Fehlgeschlagen", "cancelled": "Abgebrochen", "paused": "Pausiert" }, "printer": { "ready": "Bereit", "busy": "Beschäftigt", "error": "Fehler", "offline": "Offline", "maintenance": "Wartung" } } display_text = translations.get(type, {}).get(status, status) html = f'{display_text}' return Markup(html) @staticmethod def component_card(title: str = "", content: str = "", footer: str = "", classes: str = "", hover: bool = False) -> Markup: """ Erstellt eine Karte Args: title: Karten-Titel content: Karten-Inhalt footer: Karten-Footer classes: Zusätzliche CSS-Klassen hover: Hover-Effekt aktivieren """ base_classes = ["card"] if hover: base_classes.append("card-hover") if classes: base_classes.append(classes) html_parts = [f'
'] if title: html_parts.append(f'

{title}

') if content: html_parts.append(f'
{content}
') if footer: html_parts.append(f'
{footer}
') html_parts.append('
') return Markup("".join(html_parts)) @staticmethod def component_alert(message: str, type: str = "info", dismissible: bool = False) -> Markup: """ Erstellt eine Alert-Benachrichtigung Args: message: Alert-Nachricht type: Alert-Typ (info, success, warning, error) dismissible: Schließbar machen """ base_classes = ["alert", f"alert-{type}"] html_parts = [f'
'] if dismissible: html_parts.append('''
''') html_parts.append(f'

{message}

') if dismissible: html_parts.append('''
''') html_parts.append('
') return Markup("".join(html_parts)) @staticmethod def component_modal(modal_id: str, title: str, content: str, footer: str = "", size: str = "md") -> Markup: """ Erstellt ein Modal-Dialog Args: modal_id: Eindeutige Modal-ID title: Modal-Titel content: Modal-Inhalt footer: Modal-Footer size: Modal-Größe (sm, md, lg, xl) """ size_classes = { "sm": "max-w-md", "md": "max-w-lg", "lg": "max-w-2xl", "xl": "max-w-4xl" } max_width = size_classes.get(size, "max-w-lg") html = f''' ''' return Markup(html) @staticmethod def component_table(headers: List[str], rows: List[List[str]], classes: str = "", striped: bool = True) -> Markup: """ Erstellt eine styled Tabelle Args: headers: Tabellen-Kopfzeilen rows: Tabellen-Zeilen classes: Zusätzliche CSS-Klassen striped: Zebra-Streifen aktivieren """ html_parts = ['
'] table_classes = ["table-styled"] if classes: table_classes.append(classes) html_parts.append(f'') # Kopfzeilen html_parts.append('') for header in headers: html_parts.append(f'') html_parts.append('') # Zeilen html_parts.append('') for i, row in enumerate(rows): row_classes = "" if striped and i % 2 == 1: row_classes = 'class="bg-slate-50 dark:bg-slate-800/50"' html_parts.append(f'') for cell in row: html_parts.append(f'') html_parts.append('') html_parts.append('') html_parts.append('
{header}
{cell}
') return Markup("".join(html_parts)) @staticmethod def format_datetime_german(dt: datetime, format_str: str = "%d.%m.%Y %H:%M") -> str: """ Formatiert Datetime für deutsche Anzeige Args: dt: Datetime-Objekt format_str: Format-String """ if not dt: return "" return dt.strftime(format_str) @staticmethod def format_duration(minutes: int) -> str: """ Formatiert Dauer in Minuten zu lesbarem Format Args: minutes: Dauer in Minuten """ if not minutes: return "0 Min" if minutes < 60: return f"{minutes} Min" hours = minutes // 60 remaining_minutes = minutes % 60 if remaining_minutes == 0: return f"{hours} Std" return f"{hours} Std {remaining_minutes} Min" @staticmethod def json_encode(data: Any) -> str: """ Enkodiert Python-Daten als JSON für JavaScript Args: data: Zu enkodierendes Objekt """ return json.dumps(data, default=str, ensure_ascii=False) def register_template_helpers(app): """ Registriert alle Template-Helper bei der Flask-App Args: app: Flask-App-Instanz """ # Funktionen registrieren app.jinja_env.globals['ui_button'] = UIHelpers.component_button app.jinja_env.globals['ui_badge'] = UIHelpers.component_badge app.jinja_env.globals['ui_status_badge'] = UIHelpers.component_status_badge app.jinja_env.globals['ui_card'] = UIHelpers.component_card app.jinja_env.globals['ui_alert'] = UIHelpers.component_alert app.jinja_env.globals['ui_modal'] = UIHelpers.component_modal app.jinja_env.globals['ui_table'] = UIHelpers.component_table # Filter registrieren app.jinja_env.filters['german_datetime'] = UIHelpers.format_datetime_german app.jinja_env.filters['duration'] = UIHelpers.format_duration app.jinja_env.filters['json'] = UIHelpers.json_encode # Zusätzliche globale Variablen app.jinja_env.globals['current_year'] = datetime.now().year # Icons als globale Variablen icons = { 'check': '', 'x': '', 'plus': '', 'edit': '', 'trash': '', 'printer': '', 'dashboard': '', } app.jinja_env.globals['icons'] = icons @app.context_processor def utility_processor(): """Fügt nützliche Hilfsfunktionen zu Jinja hinzu.""" return dict( active_page=active_page, format_datetime=format_datetime, format_date=format_date, format_time=format_time, random_avatar_color=random_avatar_color, get_initials=get_initials, render_progress_bar=render_progress_bar ) def active_page(path): """ Überprüft, ob der aktuelle Pfad mit dem gegebenen Pfad übereinstimmt. """ if request.path == path: return 'active' return '' def format_datetime(value, format='%d.%m.%Y %H:%M'): """ Formatiert ein Datum mit Uhrzeit nach deutschem Format. """ if value is None: return "" if isinstance(value, str): try: value = datetime.fromisoformat(value) except ValueError: return value return value.strftime(format) def format_date(value, format='%d.%m.%Y'): """ Formatiert ein Datum nach deutschem Format. """ if value is None: return "" if isinstance(value, str): try: value = datetime.fromisoformat(value) except ValueError: return value return value.strftime(format) def format_time(value, format='%H:%M'): """ Formatiert eine Uhrzeit nach deutschem Format. """ if value is None: return "" if isinstance(value, str): try: value = datetime.fromisoformat(value) except ValueError: return value return value.strftime(format) def random_avatar_color(): """ Gibt eine zufällige Hintergrundfarbe für Avatare zurück. """ colors = [ 'bg-blue-100 text-blue-800', 'bg-green-100 text-green-800', 'bg-yellow-100 text-yellow-800', 'bg-red-100 text-red-800', 'bg-indigo-100 text-indigo-800', 'bg-purple-100 text-purple-800', 'bg-pink-100 text-pink-800', 'bg-gray-100 text-gray-800', ] return random.choice(colors) def get_initials(name, max_length=2): """ Extrahiert die Initialen eines Namens. """ if not name: return "?" parts = name.split() if len(parts) == 1: return name[0:max_length].upper() initials = "" for part in parts: if part and len(initials) < max_length: initials += part[0].upper() return initials def render_progress_bar(value, color='blue'): """ Rendert einen Fortschrittsbalken ohne Inline-Styles. Args: value (int): Der Prozentwert für den Fortschrittsbalken (0-100) color (str): Die Farbe des Balkens (blue, green, purple, red) Returns: str: HTML-Markup für den Fortschrittsbalken """ css_class = f"progress-bar-fill-{color}" # Sicherstellen, dass der Wert im gültigen Bereich liegt if value < 0: value = 0 elif value > 100: value = 100 # Erstellen des DOM-Struktur für den Fortschrittsbalken html = f"""
""" return Markup(html)