track prototypes

This commit is contained in:
Florian Förster 2026-04-22 15:43:17 +02:00
parent 5aa09727c4
commit e4ebb1ee7f
7 changed files with 2091 additions and 1 deletions

2
.gitignore vendored
View File

@ -1,5 +1,5 @@
# own # own
prototypes/ # prototypes/
data/ data/
reports/ reports/
*.code-workspace *.code-workspace

View File

@ -0,0 +1,49 @@
from typing import Any
from nicegui import ui
nested_menu = {
"Notiz": None,
"Beratungsgespräch": None,
"Neu": {"Unternehmen": None, "Individualperson": None},
}
def build_menu(
title: str,
structure: dict[str, Any],
):
def _recursive(structure: dict[str, Any]):
with ui.menu():
for label, children in structure.items():
if children:
with ui.menu_item(label, auto_close=False).props("icon=arrow_right"):
with ui.menu().props('anchor="top end" self="top start"'):
build_menu(children) # Rekursion für Untermenüs
else:
ui.menu_item(label, on_click=lambda: ui.notify(f"{label} geklickt"))
with ui.button(title, icon="add"):
_recursive(structure)
# ui.menu_item("Direkte Aktion", on_click=lambda: ui.notify("Aktion 1"))
# ui.separator()
# # Der Trick: @click.stop verhindert, dass das Hauptmenü den Klick bemerkt
# with ui.menu_item("Untermenü öffnen...", auto_close=False).props("icon=arrow_right"):
# with ui.item_section().props("side"):
# ui.icon("keyboard_arrow_right")
# with ui.menu().props('anchor="top end" self="top start"'):
# ui.menu_item("Unterpunkt A", on_click=lambda: ui.notify("A"))
# ui.menu_item("Unterpunkt B", on_click=lambda: ui.notify("B"))
# def build_menu(data):
# for label, children in data.items():
# if children:
# with ui.menu_item(label):
# with ui.menu().props('anchor="top end" self="top start"'):
# build_menu(children) # Rekursion für Untermenüs
# else:
# ui.menu_item(label, on_click=lambda l=label: ui.notify(f"{l} geklickt"))

378
prototypes/t_nice_gui.py Normal file
View File

@ -0,0 +1,378 @@
from typing import Any
from nicegui import ui
def menu():
with ui.column().classes("w-full items-center"):
ui.label("Mein Dashboard 2").classes("text-h4")
# Eine Hauptspalte für die gesamte Seite
# @ui.page("/")
# def main_page():
# menu()
# # Erste Sektion: Zwei Karten nebeneinander
# with ui.row().classes("w-full justify-center"):
# with ui.card():
# ui.label("Statistik A")
# ui.number(value=42)
# with ui.card():
# ui.label("Statistik B")
# ui.switch("Aktivieren")
# # Zweite Sektion: Eine breite Spalte darunter
# with ui.card().classes("w-full items-left"):
# ui.label("Details")
# ui.markdown("Hier stehen weitere Informationen in einer eigenen Sektion.")
# with ui.expansion("Erweiterte Einstellungen", icon="settings").classes("w-full"):
# ui.label("Hier kannst du tiefergehende Konfigurationen vornehmen.")
# # ui.input("API Schlüssel")
# ui.link("Gehe zur Einstellungs-Seite", "/settings")
# @ui.page("/settings")
# def settings_page():
# menu()
# ui.label("Einstellungen").classes("text-h4")
# ui.button("Zurück", on_click=lambda: ui.navigate.to("/"))
# Beispieldaten für die Kacheln
menu_items = [
{"title": "Dashboard", "icon": "dashboard", "route": "/dashboard", "color": "#e3f2fd"},
{"title": "Benutzer", "icon": "people", "route": "/users", "color": "#f1f8e9"},
{"title": "Einstellungen", "icon": "settings", "route": "/settings", "color": "#fff3e0"},
{"title": "Berichte", "icon": "assessment", "route": "/reports", "color": "#fce4ec"},
{"title": "Hilfe", "icon": "help", "route": "/help", "color": "#f3e5f5"},
{"title": "Abmelden", "icon": "logout", "route": "/logout", "color": "#efebe9"},
]
# @ui.page("/")
# def main_page():
# ui.label("Hauptmenü").classes("text-h4 mb-4")
# # Erstellt ein Raster mit 3 Spalten (auf kleinen Bildschirmen 1, auf mittleren 2, auf großen 3)
# with ui.grid(columns="1fr 1fr 1fr").classes("w-full gap-4"):
# for item in menu_items:
# # Erstelle eine Karte als Kachel
# # 'cursor-pointer' macht den Mauszeiger zur Hand
# with (
# ui.card()
# .tight()
# .classes("cursor-pointer hover:shadow-lg transition-shadow")
# .style(f"background-color: {item['color']}; height: 150px;")
# .on("click", lambda i=item: ui.navigate.to(i["route"]))
# ):
# with ui.column().classes("w-full h-full items-center justify-center p-4"):
# ui.icon(item["icon"]).classes("text-5xl mb-2")
# ui.label(item["title"]).classes("text-lg font-bold")
# # Dummy-Zielseiten
# @ui.page("/dashboard")
# def dashboard():
# ui.label("Hier ist das Dashboard")
# ui.button("Zurück", on_click=lambda: ui.navigate.to("/"))
# Beispieldaten: Eine Liste von Einträgen
# data = [
# {"id": "001", "name": "Projekt Alpha", "status": "Aktiv", "date": "2023-10-01"},
# {"id": "002", "name": "Kundenmeeting", "status": "Geplant", "date": "2023-10-05"},
# {"id": "003", "name": "System-Update", "status": "Erledigt", "date": "2023-09-28"},
# {"id": "004", "name": "Budget-Review", "status": "Aktiv", "date": "2023-10-12"},
# ]
# @ui.page("/")
# def table_view():
# with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-2"):
# # --- TABELLEN-KOPF ---
# # Wir nutzen ein Grid, um die Spaltenbreiten festzulegen (z.B. 10% ID, 50% Name, 20% Status, 20% Datum)
# with ui.row().classes(
# "w-full bg-slate-200 p-4 rounded-t-lg shadow-sm font-bold text-lg items-center"
# ):
# ui.label("ID").classes("w-12")
# ui.label("Bezeichnung").classes("flex-grow")
# ui.label("Status").classes("w-24")
# ui.label("Datum").classes("w-24 text-right")
# # --- TABELLEN-EINTRÄGE ---
# for entry in data:
# # Jede Zeile ist eine klickbare Karte/Reihe
# with (
# ui.card()
# .tight()
# .classes(
# "w-full hover:bg-blue-50 cursor-pointer transition-all hover:scale-[1.01]"
# )
# .on("click", lambda e=entry: ui.notify(f"Gehe zu: {e['name']}"))
# ):
# with ui.row().classes("w-full p-4 items-center"):
# # Geringfügig kleinerer Text als im Header (text-base vs text-lg)
# ui.label(entry["id"]).classes("w-12 text-slate-500 font-mono")
# ui.label(entry["name"]).classes("flex-grow font-medium")
# # Ein kleiner Chip für den Status
# ui.badge(
# entry["status"],
# color="green" if entry["status"] == "Aktiv" else "grey",
# ).classes("w-20")
# ui.label(entry["date"]).classes("w-24 text-right text-sm text-slate-400")
# 1. Unsere Datenquelle (könnte später auch eine Datenbank sein)
data = [
{
"UN/ NWP/ Kontaktperson": "Unternehmen 1",
"Individualberatung": "Max Mustermann",
"Pauschalberatung": "",
"VerlaufsprotokollDatum": "30.04.2023",
},
{
"UN/ NWP/ Kontaktperson": "Maxi Musterfrau",
"Individualberatung": "",
"Pauschalberatung": "Ausbildung",
"Datum": "30.06.2023",
},
]
nested_menu = {
"Notiz": None,
"Beratungsgespräch": None,
"Neu": {"Unternehmen": None, "Individualperson": None},
}
# nested_menu = {
# "Notiz": None,
# "Beratungsgespräch": None,
# "Neu": {"Unternehmen": None, "Individualperson": None},
# "Weitere": {
# "Sub 1": {"Sub-1-1": None, "Sub-1-2": None},
# "Sub 2": {"Sub 2-1": None, "Sub-2-2": None},
# "Sub 3 deutlich länger als andere": {"Sub 3-1": None, "Sub-3-2": None},
# },
# }
def build_menu(
title: str,
icon: str,
structure: dict[str, Any],
):
# Diese Funktion baut NUR die Einträge (Items), nicht die Container (Menus)
def _recursive(current_structure: dict[str, Any]):
for label, children in current_structure.items():
if children:
# --- AST (Verzweigung) ---
with ui.menu_item(auto_close=False):
with ui.item_section():
ui.label(label)
with ui.item_section().props("side"):
ui.icon("keyboard_arrow_right") # Das korrekte Pfeil-Icon
# Hier bauen wir EINEN neuen Container für die nächste Ebene
with ui.menu().props('anchor="top end" self="top start"'):
_recursive(children)
else:
# --- BLATT (Endpunkt) ---
# Beachte das l=label für das Closure/Late-Binding-Problem
ui.menu_item(label, on_click=lambda l=label: ui.notify(f"{l} geklickt!"))
with ui.button(title, icon=icon):
with ui.menu():
_recursive(structure)
@ui.page("/")
def table_view():
# Header-Bereich
with ui.column().classes("w-full max-w-5xl mx-auto p-2 gap-2"):
ui.label("Projekt-Verwaltung").classes("text-h4 mb-2")
with ui.row().classes("w-full justify-left p-2"):
build_menu("Hinzufügen", "add", nested_menu)
# with ui.button("Neu", icon="add"):
# with ui.menu() as main_menu:
# ui.menu_item("Direkte Aktion", on_click=lambda: ui.notify("Aktion 1"))
# ui.separator()
# with ui.menu_item("Untermenü öffnen...", auto_close=False).props(
# "icon=arrow_right"
# ):
# with ui.item_section().props("side"):
# ui.icon("keyboard_arrow_right")
# with ui.menu().props('anchor="top end" self="top start"'):
# ui.menu_item("Unterpunkt A", on_click=lambda: ui.notify("A"))
# ui.menu_item("Unterpunkt B", on_click=lambda: ui.notify("B"))
# --- EINGABE-BEREICH ---
with ui.row().classes("w-full items-center mb-4 gap-2"):
name_input = ui.input(placeholder="Name des Eintrags").classes("flex-grow")
status_input = ui.select(["Aktiv", "Geplant", "Erledigt"], value="Aktiv").classes(
"w-40"
)
def add_entry():
if not name_input.value:
ui.notify("Bitte einen Namen eingeben!", type="negative")
return
# Neuen Eintrag zum Datenmodell hinzufügen
new_id = f"{len(data) + 1:03d}"
data.append(
{
"id": new_id,
"name": name_input.value,
"status": status_input.value,
"date": "Heute",
}
)
# UI aktualisieren
name_input.value = "" # Feld leeren
# render_table.refresh() # Die Tabelle neu zeichnen
ui.notify(f"Eintrag {new_id} hinzugefügt")
ui.button(on_click=add_entry, icon="add").classes("rounded-full")
# Beispieldaten: Bei Projekt Gamma fehlt 'c3' komplett, bei Delta ist es leer/None
data = [
{
"c1": "Projekt Alpha",
"c2": "Ganz normaler Eintrag mit allen Daten.",
"c3": "Teamleitung",
"c4": "Priorität Hoch",
"date": "17.04.2024",
},
{
"c1": "Projekt Gamma",
"c2": "Hier fehlt die Abteilung (c3) komplett im Datenmodell.",
# 'c3' existiert hier nicht!
"c4": "Wartend",
"date": "21.04.2024",
},
{
"c1": "Projekt Delta",
"c2": "Hier ist die Abteilung (c3) explizit None.",
"c3": None,
"c4": "Pausiert",
"date": "22.04.2024",
},
]
# Äußerer Container mit grauem Hintergrund, damit die weißen Zellen besser wirken
# KORREKTUR: 'min-h-screen' entfernt.
# NEU: 'rounded-xl' hinzugefügt, damit der Hintergrund-Kasten saubere Ecken hat.
with ui.column().classes("w-full max-w-6xl mx-auto p-4 gap-2 bg-slate-50 rounded-xl"):
# --- TABELLEN-HEADER ---
# Auch hier min-w-0 hinzufügen, damit der Header nicht vom Text weggedrückt wird
with ui.row().classes(
"w-full p-2 font-bold text-slate-500 items-center no-wrap gap-2"
):
ui.label("Name").classes("flex-1 min-w-0 text-center")
ui.label("Beschreibung").classes("flex-1 min-w-0 text-center")
ui.label("Abteilung").classes("flex-1 min-w-0 text-center")
ui.label("Status").classes("flex-1 min-w-0 text-center")
ui.label("Datum").classes("w-32 text-center")
# --- TABELLEN-ZEILEN ---
for entry in data:
with ui.row().classes("w-full items-stretch no-wrap gap-2 mb-2"):
# WICHTIG: 'min-w-0' zwingt die Box, in ihrem Flex-Anteil zu bleiben!
cell_classes = (
"p-3 bg-white border border-slate-200 rounded-lg shadow-sm "
"cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-all "
"flex items-center justify-center text-center min-w-0"
)
text_logic = "whitespace-normal break-words"
with ui.element("div").classes(f"flex-1 {cell_classes}"):
ui.label(entry.get("c1", "")).classes(f"{text_logic} font-bold")
with ui.element("div").classes(f"flex-1 {cell_classes}"):
ui.label(entry.get("c2", "")).classes(
f"{text_logic} text-sm text-slate-600"
)
# --- ZELLE 3: PLATZHALTER ---
c3_value = entry.get("c3")
if c3_value:
with ui.element("div").classes(f"flex-1 {cell_classes}"):
ui.label(c3_value).classes(text_logic)
else:
# WICHTIG: Auch der unsichtbare Platzhalter braucht 'min-w-0'
ui.element("div").classes("flex-1 min-w-0")
with ui.element("div").classes(f"flex-1 {cell_classes}"):
ui.badge(entry.get("c4", "")).classes(text_logic)
with ui.element("div").classes(f"w-32 {cell_classes}"):
ui.label(entry.get("date", "")).classes(
f"{text_logic} text-slate-400 text-xs font-mono"
)
# # --- TABELLEN-HEADER ---
# with ui.row().classes("w-full bg-slate-200 p-4 rounded-t-lg font-bold items-center"):
# ui.label("UN/ NWP/ Kontaktperson").classes("w-12")
# ui.label("Individualberatung").classes("flex-grow")
# ui.label("Pauschalberatung").classes("w-24 text-center")
# ui.label("Aktion").classes("w-12 text-right")
# # --- ZEILEN MIT EINZELN KLICKBAREN SPALTEN ---
# for entry in data:
# with ui.card().tight().classes("w-full mb-1"):
# with ui.row().classes("w-full p-4 items-center no-wrap"):
# # 1. Spalte: ID (Klickbar)
# ui.label(entry["UN/ NWP/ Kontaktperson"]).classes(
# "flex-grow text-slate-500 font-mono cursor-pointer hover:text-blue-600"
# ).on("click", lambda e=entry: ui.notify(f"ID {e['id']} Details öffnen"))
# # 2. Spalte: Name (Klickbar, nimmt den meisten Platz ein)
# ui.label(entry["Individualberatung"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# # 3. Spalte: Status (Klickbar, als Badge)
# ui.label(entry["Pauschalberatung"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# # 4. Spalte: Einzelnes Icon (Klickbare Aktion am Rand)
# # 3. Spalte: Status (Klickbar, als Badge)
# ui.label(entry["Datum"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# --- DYNAMISCHER TABELLEN-INHALT ---
# Wir definieren eine Funktion mit dem @ui.refreshable Dekorator
# @ui.refreshable
# def render_table():
# if not data:
# ui.label("Keine Einträge vorhanden").classes("p-4 text-slate-400")
# return
# for entry in data:
# with ui.card().tight().classes("w-full hover:bg-blue-50 cursor-pointer mb-1"):
# with ui.row().classes("w-full p-4 items-center"):
# ui.label(entry["id"]).classes("w-12 text-slate-500 font-mono")
# ui.label(entry["name"]).classes("flex-grow font-medium")
# ui.badge(
# entry["status"],
# color="green" if entry["status"] == "Aktiv" else "grey",
# ).classes("w-20")
# ui.label(entry["date"]).classes(
# "w-24 text-right text-sm text-slate-400"
# )
# Initiales Rendern der Tabelle
# render_table()
ui.run(native=False)

384
prototypes/t_nice_gui_2.py Normal file
View File

@ -0,0 +1,384 @@
from typing import Any
from nicegui import ui
def menu():
with ui.column().classes("w-full items-center"):
ui.label("Mein Dashboard 2").classes("text-h4")
# Eine Hauptspalte für die gesamte Seite
# @ui.page("/")
# def main_page():
# menu()
# # Erste Sektion: Zwei Karten nebeneinander
# with ui.row().classes("w-full justify-center"):
# with ui.card():
# ui.label("Statistik A")
# ui.number(value=42)
# with ui.card():
# ui.label("Statistik B")
# ui.switch("Aktivieren")
# # Zweite Sektion: Eine breite Spalte darunter
# with ui.card().classes("w-full items-left"):
# ui.label("Details")
# ui.markdown("Hier stehen weitere Informationen in einer eigenen Sektion.")
# with ui.expansion("Erweiterte Einstellungen", icon="settings").classes("w-full"):
# ui.label("Hier kannst du tiefergehende Konfigurationen vornehmen.")
# # ui.input("API Schlüssel")
# ui.link("Gehe zur Einstellungs-Seite", "/settings")
# @ui.page("/settings")
# def settings_page():
# menu()
# ui.label("Einstellungen").classes("text-h4")
# ui.button("Zurück", on_click=lambda: ui.navigate.to("/"))
# Beispieldaten für die Kacheln
menu_items = [
{"title": "Dashboard", "icon": "dashboard", "route": "/dashboard", "color": "#e3f2fd"},
{"title": "Benutzer", "icon": "people", "route": "/users", "color": "#f1f8e9"},
{"title": "Einstellungen", "icon": "settings", "route": "/settings", "color": "#fff3e0"},
{"title": "Berichte", "icon": "assessment", "route": "/reports", "color": "#fce4ec"},
{"title": "Hilfe", "icon": "help", "route": "/help", "color": "#f3e5f5"},
{"title": "Abmelden", "icon": "logout", "route": "/logout", "color": "#efebe9"},
]
# @ui.page("/")
# def main_page():
# ui.label("Hauptmenü").classes("text-h4 mb-4")
# # Erstellt ein Raster mit 3 Spalten (auf kleinen Bildschirmen 1, auf mittleren 2, auf großen 3)
# with ui.grid(columns="1fr 1fr 1fr").classes("w-full gap-4"):
# for item in menu_items:
# # Erstelle eine Karte als Kachel
# # 'cursor-pointer' macht den Mauszeiger zur Hand
# with (
# ui.card()
# .tight()
# .classes("cursor-pointer hover:shadow-lg transition-shadow")
# .style(f"background-color: {item['color']}; height: 150px;")
# .on("click", lambda i=item: ui.navigate.to(i["route"]))
# ):
# with ui.column().classes("w-full h-full items-center justify-center p-4"):
# ui.icon(item["icon"]).classes("text-5xl mb-2")
# ui.label(item["title"]).classes("text-lg font-bold")
# # Dummy-Zielseiten
# @ui.page("/dashboard")
# def dashboard():
# ui.label("Hier ist das Dashboard")
# ui.button("Zurück", on_click=lambda: ui.navigate.to("/"))
# Beispieldaten: Eine Liste von Einträgen
# data = [
# {"id": "001", "name": "Projekt Alpha", "status": "Aktiv", "date": "2023-10-01"},
# {"id": "002", "name": "Kundenmeeting", "status": "Geplant", "date": "2023-10-05"},
# {"id": "003", "name": "System-Update", "status": "Erledigt", "date": "2023-09-28"},
# {"id": "004", "name": "Budget-Review", "status": "Aktiv", "date": "2023-10-12"},
# ]
# @ui.page("/")
# def table_view():
# with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-2"):
# # --- TABELLEN-KOPF ---
# # Wir nutzen ein Grid, um die Spaltenbreiten festzulegen (z.B. 10% ID, 50% Name, 20% Status, 20% Datum)
# with ui.row().classes(
# "w-full bg-slate-200 p-4 rounded-t-lg shadow-sm font-bold text-lg items-center"
# ):
# ui.label("ID").classes("w-12")
# ui.label("Bezeichnung").classes("flex-grow")
# ui.label("Status").classes("w-24")
# ui.label("Datum").classes("w-24 text-right")
# # --- TABELLEN-EINTRÄGE ---
# for entry in data:
# # Jede Zeile ist eine klickbare Karte/Reihe
# with (
# ui.card()
# .tight()
# .classes(
# "w-full hover:bg-blue-50 cursor-pointer transition-all hover:scale-[1.01]"
# )
# .on("click", lambda e=entry: ui.notify(f"Gehe zu: {e['name']}"))
# ):
# with ui.row().classes("w-full p-4 items-center"):
# # Geringfügig kleinerer Text als im Header (text-base vs text-lg)
# ui.label(entry["id"]).classes("w-12 text-slate-500 font-mono")
# ui.label(entry["name"]).classes("flex-grow font-medium")
# # Ein kleiner Chip für den Status
# ui.badge(
# entry["status"],
# color="green" if entry["status"] == "Aktiv" else "grey",
# ).classes("w-20")
# ui.label(entry["date"]).classes("w-24 text-right text-sm text-slate-400")
# 1. Unsere Datenquelle (könnte später auch eine Datenbank sein)
data = [
{
"UN/ NWP/ Kontaktperson": "Unternehmen 1",
"Individualberatung": "Max Mustermann",
"Pauschalberatung": "",
"VerlaufsprotokollDatum": "30.04.2023",
},
{
"UN/ NWP/ Kontaktperson": "Maxi Musterfrau",
"Individualberatung": "",
"Pauschalberatung": "Ausbildung",
"Datum": "30.06.2023",
},
]
nested_menu = {
"Notiz": None,
"Beratungsgespräch": None,
"Neu": {"Unternehmen": None, "Individualperson": None},
}
# nested_menu = {
# "Notiz": None,
# "Beratungsgespräch": None,
# "Neu": {"Unternehmen": None, "Individualperson": None},
# "Weitere": {
# "Sub 1": {"Sub-1-1": None, "Sub-1-2": None},
# "Sub 2": {"Sub 2-1": None, "Sub-2-2": None},
# "Sub 3 deutlich länger als andere": {"Sub 3-1": None, "Sub-3-2": None},
# },
# }
def build_menu(
title: str,
icon: str,
structure: dict[str, Any],
):
# Diese Funktion baut NUR die Einträge (Items), nicht die Container (Menus)
def _recursive(current_structure: dict[str, Any]):
for label, children in current_structure.items():
if children:
# --- AST (Verzweigung) ---
with ui.menu_item(auto_close=False):
with ui.item_section():
ui.label(label)
with ui.item_section().props("side"):
ui.icon("keyboard_arrow_right") # Das korrekte Pfeil-Icon
# Hier bauen wir EINEN neuen Container für die nächste Ebene
with ui.menu().props('anchor="top end" self="top start"'):
_recursive(children)
else:
# --- BLATT (Endpunkt) ---
# Beachte das l=label für das Closure/Late-Binding-Problem
ui.menu_item(label, on_click=lambda l=label: ui.notify(f"{l} geklickt!"))
with ui.button(title, icon=icon):
with ui.menu():
_recursive(structure)
@ui.page("/")
def table_view():
# Header-Bereich
with ui.column().classes("w-full max-w-5xl mx-auto p-2 gap-2"):
ui.label("Projekt-Verwaltung").classes("text-h4 mb-2")
with ui.row().classes("w-full justify-left p-2"):
build_menu("Hinzufügen", "add", nested_menu)
# with ui.button("Neu", icon="add"):
# with ui.menu() as main_menu:
# ui.menu_item("Direkte Aktion", on_click=lambda: ui.notify("Aktion 1"))
# ui.separator()
# with ui.menu_item("Untermenü öffnen...", auto_close=False).props(
# "icon=arrow_right"
# ):
# with ui.item_section().props("side"):
# ui.icon("keyboard_arrow_right")
# with ui.menu().props('anchor="top end" self="top start"'):
# ui.menu_item("Unterpunkt A", on_click=lambda: ui.notify("A"))
# ui.menu_item("Unterpunkt B", on_click=lambda: ui.notify("B"))
# --- EINGABE-BEREICH ---
with ui.row().classes("w-full items-center mb-4 gap-2"):
name_input = ui.input(placeholder="Name des Eintrags").classes("flex-grow")
status_input = ui.select(["Aktiv", "Geplant", "Erledigt"], value="Aktiv").classes(
"w-40"
)
def add_entry():
if not name_input.value:
ui.notify("Bitte einen Namen eingeben!", type="negative")
return
# Neuen Eintrag zum Datenmodell hinzufügen
new_id = f"{len(data) + 1:03d}"
data.append(
{
"id": new_id,
"name": name_input.value,
"status": status_input.value,
"date": "Heute",
}
)
# UI aktualisieren
name_input.value = "" # Feld leeren
# render_table.refresh() # Die Tabelle neu zeichnen
ui.notify(f"Eintrag {new_id} hinzugefügt")
ui.button(on_click=add_entry, icon="add").classes("rounded-full")
# Beispieldaten: Bei Projekt Gamma fehlt 'c3' komplett, bei Delta ist es leer/None
data = [
{
"c1": "Projekt Alpha",
"c2": "Ganz normaler Eintrag mit allen Daten.",
"c3": "Teamleitung",
"c4": "Priorität Hoch",
"date": "17.04.2024",
},
{
"c1": "Projekt Gamma",
"c2": "Hier fehlt die Abteilung (c3) komplett im Datenmodell.",
# 'c3' existiert hier nicht!
"c4": "Wartend",
"date": "21.04.2024",
},
{
"c1": "Projekt Delta",
"c2": "Hier ist die Abteilung (c3) explizit None.",
"c3": None,
"c4": "Pausiert",
"date": "22.04.2024",
},
]
# Äußerer Container mit grauem Hintergrund, damit die weißen Zellen besser wirken
# KORREKTUR: 'min-h-screen' entfernt.
# NEU: 'rounded-xl' hinzugefügt, damit der Hintergrund-Kasten saubere Ecken hat.
with ui.column().classes("w-full max-w-6xl mx-auto p-4 gap-2 bg-slate-50 rounded-xl"):
# --- DIE GRID-DEFINITION ---
# 4 gleich große Spalten (1fr) und eine feste (8rem = ca. 128px, entspricht w-32)
grid_classes = "w-full grid grid-cols-[1fr_1fr_1fr_1fr_8rem] gap-2"
# --- TABELLEN-HEADER ---
# Wir nutzen ein normales div mit unseren grid_classes statt ui.row()
with ui.element("div").classes(
f"{grid_classes} p-2 font-bold text-slate-500 items-center"
):
ui.label("Name").classes("text-center")
ui.label("Beschreibung").classes("text-center")
ui.label("Abteilung").classes("text-center")
ui.label("Status").classes("text-center")
ui.label("Datum").classes("text-center")
# --- TABELLEN-ZEILEN ---
for entry in data:
# Auch die Zeile wird zu einem Grid-Container. 'items-stretch' funktioniert hier ebenfalls!
with ui.element("div").classes(f"{grid_classes} items-stretch mb-2"):
# Wir brauchen hier KEIN flex-1 oder min-w-0 mehr! Grid regelt die Breite.
# Wir behalten flex nur im Inneren der Box, um den Text zu zentrieren.
cell_classes = (
"p-3 bg-white border border-slate-200 rounded-lg shadow-sm "
"cursor-pointer hover:border-blue-400 hover:bg-blue-50 transition-all "
"flex items-center justify-center text-center"
)
text_logic = "whitespace-normal break-words"
with ui.element("div").classes(cell_classes):
ui.label(entry.get("c1", "")).classes(f"{text_logic} font-bold")
with ui.element("div").classes(cell_classes):
ui.label(entry.get("c2", "")).classes(
f"{text_logic} text-sm text-slate-600"
)
# --- ZELLE 3: PLATZHALTER ---
c3_value = entry.get("c3")
if c3_value:
with ui.element("div").classes(cell_classes):
ui.label(c3_value).classes(text_logic)
else:
# Der Platzhalter braucht gar nichts mehr. Grid reserviert den Platz ohnehin!
ui.element("div")
with ui.element("div").classes(cell_classes):
ui.badge(entry.get("c4", "")).classes(text_logic)
with ui.element("div").classes(cell_classes):
ui.label(entry.get("date", "")).classes(
f"{text_logic} text-slate-400 text-xs font-mono"
)
# # --- TABELLEN-HEADER ---
# with ui.row().classes("w-full bg-slate-200 p-4 rounded-t-lg font-bold items-center"):
# ui.label("UN/ NWP/ Kontaktperson").classes("w-12")
# ui.label("Individualberatung").classes("flex-grow")
# ui.label("Pauschalberatung").classes("w-24 text-center")
# ui.label("Aktion").classes("w-12 text-right")
# # --- ZEILEN MIT EINZELN KLICKBAREN SPALTEN ---
# for entry in data:
# with ui.card().tight().classes("w-full mb-1"):
# with ui.row().classes("w-full p-4 items-center no-wrap"):
# # 1. Spalte: ID (Klickbar)
# ui.label(entry["UN/ NWP/ Kontaktperson"]).classes(
# "flex-grow text-slate-500 font-mono cursor-pointer hover:text-blue-600"
# ).on("click", lambda e=entry: ui.notify(f"ID {e['id']} Details öffnen"))
# # 2. Spalte: Name (Klickbar, nimmt den meisten Platz ein)
# ui.label(entry["Individualberatung"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# # 3. Spalte: Status (Klickbar, als Badge)
# ui.label(entry["Pauschalberatung"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# # 4. Spalte: Einzelnes Icon (Klickbare Aktion am Rand)
# # 3. Spalte: Status (Klickbar, als Badge)
# ui.label(entry["Datum"]).classes(
# "flex-grow font-medium cursor-pointer hover:underline"
# ).on("click", lambda e=entry: ui.notify(f"Editor für {e['name']}"))
# --- DYNAMISCHER TABELLEN-INHALT ---
# Wir definieren eine Funktion mit dem @ui.refreshable Dekorator
# @ui.refreshable
# def render_table():
# if not data:
# ui.label("Keine Einträge vorhanden").classes("p-4 text-slate-400")
# return
# for entry in data:
# with ui.card().tight().classes("w-full hover:bg-blue-50 cursor-pointer mb-1"):
# with ui.row().classes("w-full p-4 items-center"):
# ui.label(entry["id"]).classes("w-12 text-slate-500 font-mono")
# ui.label(entry["name"]).classes("flex-grow font-medium")
# ui.badge(
# entry["status"],
# color="green" if entry["status"] == "Aktiv" else "grey",
# ).classes("w-20")
# ui.label(entry["date"]).classes(
# "w-24 text-right text-sm text-slate-400"
# )
# Initiales Rendern der Tabelle
# render_table()
ui.run(native=False)

285
prototypes/t_qt.py Normal file
View File

@ -0,0 +1,285 @@
import sys
from PySide6.QtCore import Qt
from PySide6.QtGui import QAction # WICHTIG: QAction wird für Menüeinträge gebraucht!
from PySide6.QtWidgets import (
QApplication,
QDialog,
QDialogButtonBox,
QFormLayout,
QFrame,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QMainWindow,
QScrollArea,
QSizePolicy,
QVBoxLayout,
QWidget,
)
# 1. Wir definieren unsere eigene klickbare "Zelle"
class ClickableCell(QFrame):
def __init__(self, text):
super().__init__()
# Qt Style Sheets (QSS) für das Design (Rahmen, Hintergrund, Hover)
self.setStyleSheet("""
ClickableCell {
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
ClickableCell:hover {
background-color: #eff6ff;
border: 1px solid #60a5fa;
}
""")
# Layout in der Zelle, um den Text zu zentrieren
layout = QVBoxLayout(self)
label = QLabel(text)
label.setWordWrap(True) # Textumbruch erzwingen!
label.setAlignment(Qt.AlignCenter)
label.setStyleSheet("border: none; background: transparent;")
layout.addWidget(label)
# Klick-Event abfangen
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
print("Zelle wurde geklickt!")
class HeaderCell(QLabel):
def __init__(self, text):
super().__init__(text)
# Textausrichtung zentrieren
self.setAlignment(Qt.AlignCenter)
# Styling: Fetter Text, grauer Hintergrund (entspricht slate-200), leicht abgerundet
self.setStyleSheet("""
HeaderCell {
background-color: #e2e8f0;
color: #475569;
font-weight: bold;
padding: 10px;
border-radius: 6px;
}
""")
class NewEntryDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Neuer Eintrag")
self.setMinimumWidth(350)
# Ein FormLayout richtet Labels und Eingabefelder automatisch sauber aus
layout = QFormLayout(self)
# Eingabefelder erstellen
self.input_c1 = QLineEdit()
self.input_c2 = QLineEdit()
self.input_c3 = QLineEdit()
self.input_c4 = QLineEdit()
self.input_date = QLineEdit()
# Felder zum Layout hinzufügen
layout.addRow("Name:", self.input_c1)
layout.addRow("Beschreibung:", self.input_c2)
layout.addRow("Abteilung (optional):", self.input_c3)
layout.addRow("Status:", self.input_c4)
layout.addRow("Datum:", self.input_date)
# Standard-Buttons (OK und Abbrechen)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept) # Schließt Dialog und meldet "Erfolg"
buttons.rejected.connect(self.reject) # Schließt Dialog und meldet "Abbruch"
layout.addWidget(buttons)
def get_data(self):
# Liest die Textfelder aus und gibt sie als Dictionary zurück
return {
"c1": self.input_c1.text(),
"c2": self.input_c2.text(),
# Wenn das Feld leer ist, speichern wir None (für unseren Platzhalter-Effekt)
"c3": self.input_c3.text() if self.input_c3.text().strip() else None,
"c4": self.input_c4.text(),
"date": self.input_date.text(),
}
# 2. Das Hauptfenster mit dem Grid-Layout
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Native PySide Tabelle")
self.resize(1800, 200)
# --- 1. DAS MENÜ ERSTELLEN ---
self.create_menu()
# --- 2. DAS ZENTRALE WIDGET ---
# Da das QMainWindow den Rahmen vorgibt, brauchen wir ein Container-Widget für die Mitte
central_widget = QWidget()
self.setCentralWidget(central_widget)
# Das Haupt-Layout des Fensters (Horizontal)
outer_layout = QHBoxLayout(central_widget)
# Ein Container-Widget für deine Tabelle/Grid
container = QWidget()
# 2. NEU: Dem Container sagen: "Dehne dich so weit aus, wie du darfst!"
# container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
# --- NEU: DER SCROLL-BEREICH ---
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(
True
) # WICHTIG: Erlaubt dem Grid im Inneren, sich an die Breite anzupassen
scroll_area.setMinimumWidth(700)
scroll_area.setMaximumWidth(
1500
) # Die Breiten-Begrenzung wandert nun auf die ScrollArea
scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Optional: Rahmen der ScrollArea entfernen, damit es "flacher" und moderner aussieht
scroll_area.setFrameShape(QFrame.NoFrame)
# --- WICHTIG: VERKNÜPFUNG ---
# 1. Den Container in die ScrollArea stecken
scroll_area.setWidget(container)
# 3. Zentrierung durch "Stretches" (wie mx-auto)
# Wir fügen links und rechts vom Container Platzhalter ein
outer_layout.addStretch(1)
outer_layout.addWidget(scroll_area, stretch=100)
outer_layout.addStretch(1)
# Optional: Damit der Container oben am Rand klebt
outer_layout.setAlignment(Qt.AlignTop)
# Wir geben dem Container ein vertikales Layout
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0) # Entfernt unnötige Ränder
# Das Grid für unsere Tabelle
# Das Grid-Layout kommt in den Container
# self.grid = QGridLayout(container)
# Das Grid wird nun OHNE direkten Container erstellt
self.grid = QGridLayout()
self.grid.setSpacing(10) # Entspricht gap-2
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
self.grid.setColumnStretch(2, 1)
self.grid.setColumnStretch(3, 1)
# 3. Wir fügen das Grid oben in das vertikale Layout ein
container_layout.addLayout(self.grid)
# 4. DER ZAUBERTRICK: Wir setzen eine Feder unter das Grid.
# Diese Feder drückt das gesamte Grid nach oben und absorbiert den leeren Raum!
container_layout.addStretch()
# Zeilen-Zähler (0 ist für die Überschriften)
self.current_row = 0
# Beispieldaten (Projekt Gamma hat kein 'c3')
headers = ["Name", "Beschreibung", "Abteilung", "Status", "Datum"]
for col_idx, title in enumerate(headers):
self.grid.addWidget(HeaderCell(title), self.current_row, col_idx)
self.current_row += 1
data = [
{
"c1": "Projekt Alpha",
"c2": "Alles komplett.",
"c3": "Teamleitung",
"c4": "Hoch",
"date": "17.04.",
},
{
"c1": "Projekt Beta",
"c2": "Abteilung fehlt.",
"c4": "Wartend",
"date": "21.04.",
},
{
"c1": "Projekt Gamma",
"c2": "Alles komplett.",
"c3": "Teamleitung",
"c4": "Mittel",
"date": "30.04.",
},
{
"c1": "Projekt Delta",
"c2": "Abteilung fehlt.",
"c4": "Wartend",
"date": "05.05.",
},
]
# Wir gehen die Datenliste mit enumerate durch, um den row_index für das Grid zu bekommen
for entry in data:
self.add_row_to_grid(entry)
# --- HILFSMETHODE UM EINE ZEILE EINZUFÜGEN ---
def add_row_to_grid(self, entry):
row = self.current_row
self.grid.addWidget(ClickableCell(entry.get("c1", "")), row, 0)
self.grid.addWidget(ClickableCell(entry.get("c2", "")), row, 1)
c3_value = entry.get("c3")
if c3_value:
self.grid.addWidget(ClickableCell(c3_value), row, 2)
else:
empty_box = QFrame()
empty_box.setStyleSheet(
"QFrame { background-color: #f8fafc; border: 2px dashed #e2e8f0; border-radius: 8px; }"
)
self.grid.addWidget(empty_box, row, 2)
self.grid.addWidget(ClickableCell(entry.get("c4", "")), row, 3)
self.grid.addWidget(ClickableCell(entry.get("date", "")), row, 4)
self.current_row += 1 # Zähler für den nächsten Eintrag erhöhen
# --- MENÜ LOGIK ---
def create_menu(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("Datei")
new_action = QAction("Neuer Eintrag...", self)
new_action.setShortcut("Ctrl+N")
# VERKNÜPFUNG: Wenn geklickt, rufe die Methode zum Öffnen des Dialogs auf
new_action.triggered.connect(self.open_new_entry_dialog)
file_menu.addAction(new_action)
file_menu.addSeparator()
exit_action = QAction("Beenden", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 2. Hauptmenü-Punkt: "Hilfe"
help_menu = menu_bar.addMenu("Hilfe")
about_action = QAction("Über", self)
help_menu.addAction(about_action)
# --- DIALOG AUFRUFEN & DATEN ÜBERNEHMEN ---
def open_new_entry_dialog(self):
dialog = NewEntryDialog(self)
# .exec() pausiert das Programm, bis der Dialog geschlossen wird.
# Es gibt True zurück, wenn der Nutzer "OK" geklickt hat.
if dialog.exec():
new_data = dialog.get_data() # Dictionary aus dem Dialog holen
self.add_row_to_grid(new_data) # Ins Grid zeichnen
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

966
prototypes/t_qt_2.py Normal file
View File

@ -0,0 +1,966 @@
from __future__ import annotations
import dataclasses as dc
import sys
from PySide6.QtCore import Qt, Signal # Signal ist wichtig!
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (
QApplication,
QComboBox,
QCompleter,
QDialog,
QDialogButtonBox,
QFormLayout,
QFrame,
QGridLayout,
QHBoxLayout,
QLabel,
QLineEdit,
QListWidget,
QMainWindow,
QPlainTextEdit,
QPushButton,
QScrollArea,
QSizePolicy,
QStackedWidget,
QVBoxLayout,
QWidget,
)
@dc.dataclass(slots=True)
class Address:
name: str
street: str
number: int
zip_code: str
city: str
def export(self):
data = {}
for f in dc.fields(self):
data[f.name] = str(getattr(self, f.name))
return data
# added search field
class AddressForm(QWidget):
def __init__(self):
super().__init__()
main_layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.setSpacing(15)
# --- 1. Einfaches Feld: Firmenname ---
self.company_input = QLineEdit(placeholderText="Name des Partners")
form_layout.addRow("Name Unternehmen/Netzwerkpartner:", self.company_input)
# --- 2. Kombinierte Zeile: Straße & Hausnummer ---
street_layout = QHBoxLayout()
street_layout.setContentsMargins(0, 0, 0, 0) # Wichtig: Verhindert doppelte Abstände!
street_layout.setSpacing(10)
self.street_input = QLineEdit(placeholderText="Straße")
self.number_input = QLineEdit(placeholderText="Nr.")
# Optik-Trick: Hausnummern-Feld begrenzen, damit es nicht so breit wie die Straße wird
self.number_input.setMaximumWidth(80)
# Mit "stretch" definieren wir das Breitenverhältnis (Straße nimmt restlichen Platz)
street_layout.addWidget(self.street_input, stretch=3)
street_layout.addWidget(self.number_input, stretch=1)
form_layout.addRow("Straße / Nr.:", street_layout)
# --- 3. Kombinierte Zeile: PLZ & Ort ---
city_layout = QHBoxLayout()
city_layout.setContentsMargins(0, 0, 0, 0)
city_layout.setSpacing(10)
self.zip_input = QLineEdit(placeholderText="PLZ")
self.city_input = QLineEdit(placeholderText="Ort")
self.zip_input.setMaximumWidth(100) # PLZ ist immer relativ kurz
city_layout.addWidget(self.zip_input, stretch=1)
city_layout.addWidget(self.city_input, stretch=3)
form_layout.addRow("PLZ / Ort:", city_layout)
main_layout.addLayout(form_layout)
self.autofilled_fields = (
self.street_input,
self.number_input,
self.zip_input,
self.city_input,
)
for field in self.autofilled_fields:
field.setReadOnly(True)
field.setStyleSheet("""
QLineEdit {
background-color: #f1f5f9; /* Helles System-Grau */
color: #333D4B; /* Etwas blassere Schrift */
border: 1px dashed #cbd5e1; /* Ein gestrichelter Rand wirkt oft wie ein "Stempel" */
border-radius: 4px;
padding: 5px;
}
/* Wenn das Feld fokussiert wird, keinen blauen Rand anzeigen */
QLineEdit:focus {
border: 1px dashed #cbd5e1;
}
""")
def fill_out(self, address: Address):
addr_ = address.export()
for field, value in zip(self.autofilled_fields, addr_.values()):
field.setText(value)
ADDRESSES = [
Address("Test UG", "Teststraße", 1, "09111", "Chemnitz"),
Address("Max Mustermann GmbH", "Teststraße", 2, "09112", "Chemnitz"),
Address("Mustergruppe GbR", "Teststraße", 3, "09113", "Chemnitz"),
Address("Lorem Ipsum AG", "Teststraße", 4, "09114", "Chemnitz"),
]
class AddressForm_Search(QWidget):
def __init__(self):
super().__init__()
main_layout = QVBoxLayout(self)
form_layout = QFormLayout()
form_layout.setSpacing(15)
self.search_input = QLineEdit(placeholderText="Tippen zum Suchen...")
form_layout.addRow("Suche:", self.search_input)
search_data = [addr.name for addr in ADDRESSES]
self.SEARCH_MAP = {addr.name: addr for addr in ADDRESSES}
self.completer = QCompleter(search_data)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
self.completer.setFilterMode(Qt.MatchContains)
self.search_input.setCompleter(self.completer)
self.completer.activated.connect(self.search_result_selected)
# --- 1. Einfaches Feld: Firmenname ---
self.company_input = QLineEdit(placeholderText="Name des Partners")
form_layout.addRow("Name Unternehmen/Netzwerkpartner:", self.company_input)
street_layout = QHBoxLayout()
street_layout.setContentsMargins(0, 0, 0, 0) # Wichtig: Verhindert doppelte Abstände!
street_layout.setSpacing(10)
self.street_input = QLineEdit(placeholderText="Straße")
self.number_input = QLineEdit(placeholderText="Nr.")
# Optik-Trick: Hausnummern-Feld begrenzen, damit es nicht so breit wie die Straße wird
self.number_input.setMaximumWidth(80)
# Mit "stretch" definieren wir das Breitenverhältnis (Straße nimmt restlichen Platz)
street_layout.addWidget(self.street_input, stretch=3)
street_layout.addWidget(self.number_input, stretch=1)
form_layout.addRow("Straße / Nr.:", street_layout)
# --- 3. Kombinierte Zeile: PLZ & Ort ---
city_layout = QHBoxLayout()
city_layout.setContentsMargins(0, 0, 0, 0)
city_layout.setSpacing(10)
self.zip_input = QLineEdit(placeholderText="PLZ")
self.city_input = QLineEdit(placeholderText="Ort")
self.zip_input.setMaximumWidth(100) # PLZ ist immer relativ kurz
city_layout.addWidget(self.zip_input, stretch=1)
city_layout.addWidget(self.city_input, stretch=3)
form_layout.addRow("PLZ / Ort:", city_layout)
main_layout.addLayout(form_layout)
self.autofilled_fields = (
self.company_input,
self.street_input,
self.number_input,
self.zip_input,
self.city_input,
)
for field in self.autofilled_fields:
field.setReadOnly(True)
field.setStyleSheet("""
QLineEdit {
background-color: #f1f5f9; /* Helles System-Grau */
color: #333D4B; /* Etwas blassere Schrift */
border: 1px dashed #cbd5e1; /* Ein gestrichelter Rand wirkt oft wie ein "Stempel" */
border-radius: 4px;
padding: 5px;
}
/* Wenn das Feld fokussiert wird, keinen blauen Rand anzeigen */
QLineEdit:focus {
border: 1px dashed #cbd5e1;
}
""")
def fill_out(self, address: Address):
addr_ = address.export()
for field, value in zip(self.autofilled_fields, addr_.values()):
field.setText(value)
def search_result_selected(self, name):
address = self.SEARCH_MAP[name]
self.fill_out(address)
class DropdownSearch(QWidget):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
# 1. Das normale Eingabefeld
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Tippe zum Suchen (z.B. 'Pro')...")
self.search_input.setMinimumWidth(300)
# 2. Deine Datenbank / Liste an Suchbegriffen
search_data = [
"Projekt Alpha",
"Projekt Beta",
"Personalakte Müller",
"Protokoll April 2026",
"Abrechnung",
]
# 3. Den Completer erstellen und mit Daten füttern
self.completer = QCompleter(search_data)
# --- WICHTIGE EINSTELLUNGEN ---
# Ignoriert Groß-/Kleinschreibung (sehr wichtig für eine gute Suche!)
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
# 'MatchContains' sorgt dafür, dass "pha" auch "Projekt Alpha" findet.
# Standard ist 'MatchStartsWith' (findet nur Worte am Anfang).
self.completer.setFilterMode(Qt.MatchContains)
# 4. Den Completer an das Eingabefeld binden
self.search_input.setCompleter(self.completer)
layout.addWidget(self.search_input)
# Optional: Aktion auslösen, wenn ein Element im Dropdown angeklickt wird
self.completer.activated.connect(self.on_item_selected)
def on_item_selected(self, text):
print(f"Nutzer hat '{text}' aus dem Dropdown ausgewählt!")
# Hier könntest du z.B. deine Detail-Seite für dieses Projekt öffnen
class MyForm(QWidget):
def __init__(self):
super().__init__()
# Das Herzstück: Das Form-Layout
# Es kümmert sich automatisch darum, dass alle Labels links
# und alle Felder rechts perfekt bündig untereinander stehen.
self.form_layout = QFormLayout(self)
self.form_layout.setSpacing(15) # Abstand zwischen den Zeilen
# Definition deiner Felder
# 'key' ist der Name, unter dem du die Daten später abrufst
# 'label' ist der Text, der angezeigt wird
# 'type' bestimmt, welches Widget erstellt wird
self.field_definitions = [
{"key": "name", "label": "Projektname:", "type": "text"},
{"key": "date", "label": "Datum:", "type": "date", "value": "22.04.2026"},
{
"key": "status",
"label": "Status:",
"type": "text",
"placeholder": "z.B. Aktiv",
},
{"key": "desc", "label": "Beschreibung:", "type": "longtext"},
{"key": "notes", "label": "Interne Notizen:", "type": "longtext"},
]
# Dictionary, um die erstellten Widgets zu speichern (für späteren Zugriff)
self.widgets = {}
# Automatischer Aufbau des Formulars
self.create_form_fields()
def create_form_fields(self):
for field in self.field_definitions:
widget = None
# Entscheidung: Welches Widget wird benötigt?
if field["type"] == "text":
widget = QLineEdit()
if "placeholder" in field:
widget.setPlaceholderText(field["placeholder"])
if "value" in field:
widget.setText(field["value"])
widget.setReadOnly(True) # Falls es ein Fixwert ist
elif field["type"] == "longtext":
widget = QPlainTextEdit()
widget.setMaximumHeight(80) # Kompakte Höhe für Formulare
elif field["type"] == "date":
widget = QLineEdit() # Oder QDateEdit
widget.setText(field.get("value", ""))
widget.setReadOnly(True)
widget.setStyleSheet("background-color: #f1f5f9; border: 1px dashed #cbd5e1;")
if widget:
# Widget im Dictionary speichern, um später darauf zuzugreifen
self.widgets[field["key"]] = widget
# Dem Form-Layout hinzufügen (Label links, Widget rechts)
self.form_layout.addRow(field["label"], widget)
def get_form_data(self):
"""Liest alle Felder automatisch aus"""
data = {}
for key, widget in self.widgets.items():
if isinstance(widget, QLineEdit):
data[key] = widget.text()
elif isinstance(widget, QPlainTextEdit):
data[key] = widget.toPlainText()
return data
class ClickableCell(QFrame):
# Wir definieren ein Signal, das ein Dictionary (die Daten) mitschickt
clicked = Signal(dict)
def __init__(self, text, data_record):
super().__init__()
self.data_record = data_record # Wir merken uns den ganzen Datensatz
self.setStyleSheet("""
ClickableCell {
background-color: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
}
ClickableCell:hover {
background-color: #eff6ff;
border: 1px solid #60a5fa;
}
""")
layout = QVBoxLayout(self)
label = QLabel(text)
label.setWordWrap(True)
label.setAlignment(Qt.AlignCenter)
layout.addWidget(label)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
# Wenn geklickt wird, senden wir die Daten aus
self.clicked.emit(self.data_record)
class HeaderCell(QLabel):
def __init__(self, text):
super().__init__(text)
# Textausrichtung zentrieren
self.setAlignment(Qt.AlignCenter)
# Styling: Fetter Text, grauer Hintergrund (entspricht slate-200), leicht abgerundet
self.setStyleSheet("""
HeaderCell {
background-color: #e2e8f0;
color: #475569;
font-weight: bold;
padding: 10px;
border-radius: 6px;
}
""")
class NewEntryDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Neuer Eintrag")
self.setMinimumWidth(350)
# Ein FormLayout richtet Labels und Eingabefelder automatisch sauber aus
layout = QFormLayout(self)
# Eingabefelder erstellen
self.input_c1 = QLineEdit()
self.input_c2 = QLineEdit()
self.input_c3 = QLineEdit()
self.input_c4 = QLineEdit()
self.input_date = QLineEdit()
# Felder zum Layout hinzufügen
layout.addRow("Name:", self.input_c1)
layout.addRow("Beschreibung:", self.input_c2)
layout.addRow("Abteilung (optional):", self.input_c3)
layout.addRow("Status:", self.input_c4)
layout.addRow("Datum:", self.input_date)
# Standard-Buttons (OK und Abbrechen)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept) # Schließt Dialog und meldet "Erfolg"
buttons.rejected.connect(self.reject) # Schließt Dialog und meldet "Abbruch"
layout.addWidget(buttons)
def get_data(self):
# Liest die Textfelder aus und gibt sie als Dictionary zurück
return {
"c1": self.input_c1.text(),
"c2": self.input_c2.text(),
# Wenn das Feld leer ist, speichern wir None (für unseren Platzhalter-Effekt)
"c3": self.input_c3.text() if self.input_c3.text().strip() else None,
"c4": self.input_c4.text(),
"date": self.input_date.text(),
}
# --- 2. DIE DETAIL-ANSICHT (SEITE 2) ---
class DetailView(QWidget):
back_requested = Signal() # Signal für den Zurück-Button
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
# Zurück-Button
back_btn = QPushButton("← Zurück zur Tabelle")
back_btn.setFixedWidth(200)
back_btn.clicked.connect(lambda: self.back_requested.emit())
layout.addWidget(back_btn)
# Platzhalter für die Details
self.title_label = QLabel("Titel")
self.title_label.setStyleSheet(
"font-size: 24px; font-weight: bold; margin-top: 20px;"
)
layout.addWidget(self.title_label)
self.info_label = QLabel("Zusatzinfos...")
self.info_label.setWordWrap(True)
self.info_label.setStyleSheet("font-size: 16px; color: #475569; margin-top: 10px;")
layout.addWidget(self.info_label)
def update_content(self, data):
# Diese Methode füllt die Seite mit den echten Daten
self.title_label.setText(f"Details für: {data.get('c1', 'Unbekannt')}")
self.info_label.setText(
f"Beschreibung: {data.get('c2')}\n\n"
f"Abteilung: {data.get('c3', 'Keine Angabe')}\n"
f"Status: {data.get('c4')}\n"
f"Datum: {data.get('date')}"
)
class NewEntrySelect_view(QWidget):
back_requested = Signal() # Signal für den Zurück-Button
company_requested = Signal() # Signal Unternehmen
person_requested = Signal() # Signal Unternehmen
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
layout.setAlignment(Qt.AlignTop | Qt.AlignLeft)
# Zurück-Button
back_btn = QPushButton("← Zurück zur Übersicht")
back_btn.setFixedWidth(200)
back_btn.clicked.connect(lambda: self.back_requested.emit())
# Platzhalter für die Details
self.title_label = QLabel("Wählen Sie den Typ der Grunderfassung")
self.title_label.setStyleSheet(
"font-size: 24px; font-weight: bold; margin-top: 20px;"
)
btn_company = QPushButton("Unternehmen →")
btn_company.setFixedWidth(300)
btn_company.setFixedHeight(40)
btn_company.clicked.connect(lambda: self.company_requested.emit())
# btn_company.clicked.connect(lambda: print("Unternehmen gewählt"))
btn_person = QPushButton("Individualperson →")
btn_person.setFixedWidth(300)
btn_person.setFixedHeight(40)
btn_person.clicked.connect(lambda: self.person_requested.emit())
btn_person.clicked.connect(lambda: print("Person gewählt"))
layout.addWidget(back_btn)
layout.addSpacing(15)
layout.addWidget(self.title_label)
layout.addWidget(btn_company)
layout.addWidget(btn_person)
# self.info_label = QLabel("Zusatzinfos...")
# self.info_label.setWordWrap(True)
# self.info_label.setStyleSheet("font-size: 16px; color: #475569; margin-top: 10px;")
# layout.addWidget(self.info_label)
# def update_content(self, data):
# # Diese Methode füllt die Seite mit den echten Daten
# self.title_label.setText(f"Details für: {data.get('c1', 'Unbekannt')}")
# self.info_label.setText(
# f"Beschreibung: {data.get('c2')}\n\n"
# f"Abteilung: {data.get('c3', 'Keine Angabe')}\n"
# f"Status: {data.get('c4')}\n"
# f"Datum: {data.get('date')}"
# )
class SearchFormPage(QWidget):
back_main_requested = Signal() # Signal für den Zurück-Button
back_requested = Signal() # Signal für den Zurück-Button
def __init__(self):
super().__init__()
# Hauptlayout der Seite
main_layout = QVBoxLayout(self)
# main_layout.setContentsMargins(0, 0, 0, 0)
# --- 1. HEADER ---
header_layout = QVBoxLayout()
upper_button_group_v = QVBoxLayout()
upper_button_group = QHBoxLayout()
upper_button_group.addLayout(upper_button_group_v)
upper_button_group.addStretch()
# header_layout = QGridLayout()
# header_layout.setColumnStretch(0, 1)
# header_layout.setColumnStretch(1, 1)
back_btn_main = QPushButton("← Zurück zur Übersicht")
back_btn_main.clicked.connect(lambda: self.back_main_requested.emit())
back_btn_step = QPushButton("← Zurück")
back_btn_step.clicked.connect(lambda: self.back_requested.emit())
title = QLabel("Grunderfassung Unternehmen")
title.setStyleSheet("font-size: 20px; font-weight: bold;")
upper_button_group_v.addWidget(back_btn_step)
upper_button_group_v.addWidget(back_btn_main)
header_layout.addLayout(upper_button_group)
header_layout.addSpacing(15)
# header_layout.addWidget(back_btn_step)
header_layout.addWidget(title)
# header_layout.addStretch() # Drückt den Titel nach links
main_layout.addLayout(header_layout)
# --- KOPF Unternehmen ---
inf_block_1 = QHBoxLayout()
inhalte = [
"Fall-Nr.:",
"Ersteintrag Datum:",
"Aktualisierung Datum:",
"Aktualisierung Nutzer:",
]
for entry in inhalte:
label = QLabel(entry)
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
field = QLineEdit(placeholderText="...")
field.setText("22.04.2026")
field.setReadOnly(True)
field.setStyleSheet("""
QLineEdit {
background-color: #f1f5f9; /* Helles System-Grau */
color: #333D4B; /* Etwas blassere Schrift */
border: 1px dashed #cbd5e1; /* Ein gestrichelter Rand wirkt oft wie ein "Stempel" */
border-radius: 4px;
padding: 5px;
}
/* Wenn das Feld fokussiert wird, keinen blauen Rand anzeigen */
QLineEdit:focus {
border: 1px #cbd5e1;
}
""")
field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
inf_block_1.addWidget(label)
inf_block_1.addWidget(field)
inf_block_1.addStretch()
main_layout.addLayout(inf_block_1)
# --- NOTIZEN Unternehmen ---
# eventuell später verknüpft
inf_block_2 = QHBoxLayout()
label = QLabel("Notizen:")
label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
inf_block_2.addWidget(label, alignment=Qt.AlignmentFlag.AlignTop)
inf_block_2.addWidget(QPlainTextEdit(placeholderText="Notizen ergänzen..."))
main_layout.addLayout(inf_block_2)
main_layout.addSpacing(10)
# --- Suche mit Namen
# inf_block_3 = (
# QVBoxLayout()
# ) # Horizontal, damit Suchen und Filtern nebeneinander stehen
# name_input = QLineEdit(placeholderText="Tippe zum Suchen...")
# name_input.setMinimumWidth(150)
# name_input.setMaximumWidth(600)
# inf_block_3_1 = QHBoxLayout()
# inf_block_3_1.addWidget(QLabel("Name"))
# inf_block_3_1.addWidget(name_input, stretch=100)
# inf_block_3_1.addStretch()
# inf_block_3_2 = QHBoxLayout()
# inf_block_3_3 = QHBoxLayout()
# demo_data = {
# "name": "Test UG",
# "Straße": "Teststraße",
# "Hausnummer": "12",
# "PLZ": "09111",
# "Ort": "Chemnitz",
# }
# current_block = inf_block_3_2
# for entry in ("Straße", "Hausnummer", "PLZ", "Ort"):
# if entry == "PLZ":
# current_block = inf_block_3_3
# label = QLabel(entry)
# label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# field = QLineEdit()
# field.setText(demo_data[entry])
# field.setReadOnly(True)
# field.setStyleSheet("""
# QLineEdit {
# background-color: #f1f5f9; /* Helles System-Grau */
# color: #333D4B; /* Etwas blassere Schrift */
# border: 1px dashed #cbd5e1; /* Ein gestrichelter Rand wirkt oft wie ein "Stempel" */
# border-radius: 4px;
# padding: 5px;
# }
# /* Wenn das Feld fokussiert wird, keinen blauen Rand anzeigen */
# QLineEdit:focus {
# border: 1px dashed #cbd5e1;
# }
# """)
# if entry in ("Hausnummer", "PLZ"):
# field.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
# field.setMinimumWidth(50)
# field.setMaximumWidth(50)
# else:
# field.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
# field.setMinimumWidth(100)
# field.setMaximumWidth(300)
# current_block.addWidget(label)
# current_block.addWidget(field, stretch=100)
# inf_block_3_2.addStretch()
# inf_block_3_3.addStretch()
# inf_block_3.addLayout(inf_block_3_1)
# inf_block_3.addLayout(inf_block_3_2)
# inf_block_3.addLayout(inf_block_3_3)
# main_layout.addLayout(inf_block_3)
main_layout.addSpacing(30)
main_layout.addWidget(MyForm())
main_layout.addSpacing(30)
# addr = Address("Test UG", "Teststraße", 202, "09111", "Chemnitz")
# addr_widget = AddressForm()
# addr_widget.fill_out(addr)
addr_widget = AddressForm_Search()
main_layout.addWidget(addr_widget)
main_layout.addSpacing(30)
main_layout.addWidget(DropdownSearch())
main_layout.addSpacing(30)
# --- 2. SUCH-FORMULAR ---
form_layout = (
QHBoxLayout()
) # Horizontal, damit Suchen und Filtern nebeneinander stehen
# Texteingabe für die Suche
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Tippe zum Suchen...")
self.search_input.textChanged.connect(self.perform_search) # LIVE-SUCHE!
# Ein Dropdown als Filter-Beispiel
self.status_filter = QComboBox()
self.status_filter.addItems(["Alle", "Aktiv", "Wartend", "Abgeschlossen"])
self.status_filter.currentTextChanged.connect(self.perform_search)
form_layout.addWidget(QLabel("Suchbegriff:"))
form_layout.addWidget(self.search_input, stretch=2)
form_layout.addWidget(QLabel("Status:"))
form_layout.addWidget(self.status_filter, stretch=1)
main_layout.addLayout(form_layout)
# --- 3. ERGEBNIS-BEREICH ---
# Für den Anfang ein einfaches Listen-Widget
self.results_list = QListWidget()
main_layout.addWidget(
self.results_list, stretch=100
) # Nimmt den restlichen Platz ein
# Dummy-Datenbank für das Beispiel
self.database = [
"Projekt Alpha (Aktiv)",
"Projekt Beta (Abgeschlossen)",
"Projekt Gamma (Wartend)",
]
self.perform_search() # Initiale Ansicht laden
def perform_search(self):
# 1. Eingaben auslesen
query = self.search_input.text().lower()
status = self.status_filter.currentText()
# 2. Alte Ergebnisse löschen
self.results_list.clear()
# 3. Daten filtern und anzeigen
for item in self.database:
# Einfache Filter-Logik
if query in item.lower():
if status == "Alle" or status in item:
self.results_list.addItem(item)
# 2. Das Hauptfenster mit dem Grid-Layout
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Master")
self.resize(1800, 200)
# --- 1. DAS MENÜ ERSTELLEN ---
self.create_menu()
# DER STACK (Stapel)
self.stack = QStackedWidget()
self.setCentralWidget(self.stack)
# SEITE 1: Die Tabellen-Ansicht (unser bisheriger Code)
self.main_page = self.setup_main_page()
self.stack.addWidget(self.main_page)
# SEITE 2: Die Detail-Ansicht
self.detail_page = DetailView()
self.detail_page.back_requested.connect(self.show_main_page)
self.stack.addWidget(self.detail_page)
# SEITE: Neue Einträge hinzufügen
self.new_entry_select = NewEntrySelect_view()
self.new_entry_select.back_requested.connect(self.show_main_page)
self.new_entry_select.company_requested.connect(self.show_company_page)
self.stack.addWidget(self.new_entry_select)
# SEITE: Bsp.
self.company_page = SearchFormPage()
self.company_page.back_main_requested.connect(self.show_main_page)
self.company_page.back_requested.connect(self.show_new_entry_select)
self.stack.addWidget(self.company_page)
def setup_main_page(self):
# --- 2. DAS ZENTRALE WIDGET ---
# Da das QMainWindow den Rahmen vorgibt, brauchen wir ein Container-Widget für die Mitte
main_widget = QWidget()
# self.setCentralWidget(central_widget)
# Das Haupt-Layout des Fensters (Horizontal)
outer_layout = QHBoxLayout(main_widget)
vert_layout = QVBoxLayout()
# add button
new_btn = QPushButton("Neu →")
new_btn.setFixedWidth(100)
new_btn.setFixedHeight(40)
new_btn.clicked.connect(self.show_new_entry_select)
# back_btn.clicked.connect(lambda: self.back_requested.emit())
# layout.addWidget(back_btn)
# Ein Container-Widget für deine Tabelle/Grid
container = QWidget()
# 2. NEU: Dem Container sagen: "Dehne dich so weit aus, wie du darfst!"
# container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
# SCROLL-BEREICH
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(
True
) # WICHTIG: Erlaubt dem Grid im Inneren, sich an die Breite anzupassen
scroll_area.setMinimumWidth(700)
scroll_area.setMaximumWidth(
1500
) # Die Breiten-Begrenzung wandert nun auf die ScrollArea
scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Optional: Rahmen der ScrollArea entfernen, damit es "flacher" und moderner aussieht
scroll_area.setFrameShape(QFrame.NoFrame)
scroll_area.setWidget(container)
vert_layout.addWidget(new_btn)
vert_layout.addSpacing(20)
vert_layout.addWidget(scroll_area)
# Zentrierung durch "Stretches" (wie mx-auto)
# Wir fügen links und rechts vom Container Platzhalter ein
outer_layout.addStretch(1)
outer_layout.addLayout(vert_layout, stretch=100)
# outer_layout.addWidget(scroll_area, stretch=100)
outer_layout.addStretch(1)
# Optional: Damit der Container oben am Rand klebt
outer_layout.setAlignment(Qt.AlignTop)
# Wir geben dem Container ein vertikales Layout
container_layout = QVBoxLayout(container)
container_layout.setContentsMargins(0, 0, 0, 0) # Entfernt unnötige Ränder
# Das Grid für unsere Tabelle
# Das Grid-Layout kommt in den Container
# self.grid = QGridLayout(container)
# Das Grid wird nun OHNE direkten Container erstellt
self.grid = QGridLayout()
self.grid.setSpacing(10) # Entspricht gap-2
self.grid.setColumnStretch(0, 1)
self.grid.setColumnStretch(1, 1)
self.grid.setColumnStretch(2, 1)
self.grid.setColumnStretch(3, 1)
# 3. Wir fügen das Grid oben in das vertikale Layout ein
container_layout.addLayout(self.grid)
# 4. DER ZAUBERTRICK: Wir setzen eine Feder unter das Grid.
# Diese Feder drückt das gesamte Grid nach oben und absorbiert den leeren Raum!
container_layout.addStretch()
# Zeilen-Zähler (0 ist für die Überschriften)
self.current_row = 0
# Beispieldaten (Projekt Gamma hat kein 'c3')
headers = ["Name", "Beschreibung", "Abteilung", "Status", "Datum"]
for col_idx, title in enumerate(headers):
self.grid.addWidget(HeaderCell(title), self.current_row, col_idx)
self.current_row += 1
data = [
{
"c1": "Projekt Alpha",
"c2": "Alles komplett.",
"c3": "Teamleitung",
"c4": "Hoch",
"date": "17.04.",
},
{
"c1": "Projekt Beta",
"c2": "Abteilung fehlt.",
"c4": "Wartend",
"date": "21.04.",
},
{
"c1": "Projekt Gamma",
"c2": "Alles komplett.",
"c3": "Teamleitung",
"c4": "Mittel",
"date": "30.04.",
},
{
"c1": "Projekt Delta",
"c2": "Abteilung fehlt.",
"c4": "Wartend",
"date": "05.05.",
},
]
# Wir gehen die Datenliste mit enumerate durch, um den row_index für das Grid zu bekommen
for entry in data:
self.add_row_to_grid(entry)
return main_widget
# --- HILFSMETHODE UM EINE ZEILE EINZUFÜGEN ---
def add_row_to_grid(self, entry):
row = self.current_row
# Beim Erstellen der Zelle übergeben wir den kompletten Datensatz
cell = ClickableCell(entry["c1"], entry)
cell.clicked.connect(self.show_details)
self.grid.addWidget(cell, row, 0)
# Wir verbinden das Klick-Signal der Zelle mit unserer Wechsel-Funktion
self.grid.addWidget(ClickableCell(entry["c2"], entry), row, 1)
c3_value = entry.get("c3")
if c3_value:
self.grid.addWidget(ClickableCell(c3_value, entry), row, 2)
else:
empty_box = QFrame()
empty_box.setStyleSheet(
"QFrame { background-color: #f8fafc; border: 2px dashed #e2e8f0; border-radius: 8px; }"
)
self.grid.addWidget(empty_box, row, 2)
self.grid.addWidget(ClickableCell(entry.get("c4", ""), entry), row, 3)
self.grid.addWidget(ClickableCell(entry.get("date", ""), entry), row, 4)
self.current_row += 1 # Zähler für den nächsten Eintrag erhöhen
def show_details(self, data):
# 1. Daten an die Detail-Seite übergeben
self.detail_page.update_content(data)
# 2. Auf die Detail-Seite umblättern
self.stack.setCurrentWidget(self.detail_page)
def show_main_page(self):
# Zurück zur Tabelle blättern
self.stack.setCurrentWidget(self.main_page)
def show_new_entry_select(self):
self.stack.setCurrentWidget(self.new_entry_select)
def show_company_page(self):
self.stack.setCurrentWidget(self.company_page)
# --- MENÜ LOGIK ---
def create_menu(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu("Datei")
new_action = QAction("Neuer Eintrag...", self)
new_action.setShortcut("Ctrl+N")
# VERKNÜPFUNG: Wenn geklickt, rufe die Methode zum Öffnen des Dialogs auf
new_action.triggered.connect(self.open_new_entry_dialog)
file_menu.addAction(new_action)
file_menu.addSeparator()
exit_action = QAction("Beenden", self)
exit_action.setShortcut("Ctrl+Q")
exit_action.triggered.connect(self.close)
file_menu.addAction(exit_action)
# 2. Hauptmenü-Punkt: "Hilfe"
help_menu = menu_bar.addMenu("Hilfe")
about_action = QAction("Über", self)
help_menu.addAction(about_action)
# --- DIALOG AUFRUFEN & DATEN ÜBERNEHMEN ---
def open_new_entry_dialog(self):
dialog = NewEntryDialog(self)
# .exec() pausiert das Programm, bis der Dialog geschlossen wird.
# Es gibt True zurück, wenn der Nutzer "OK" geklickt hat.
if dialog.exec():
new_data = dialog.get_data() # Dictionary aus dem Dialog holen
self.add_row_to_grid(new_data) # Ins Grid zeichnen
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())

28
prototypes/tests.py Normal file
View File

@ -0,0 +1,28 @@
# %%
import dataclasses as dc
# %%
@dc.dataclass(slots=True)
class Address:
street: str
number: int
postal_code: str
city: str
def export(self):
data = {}
for f in dc.fields(self):
val = getattr(self, f.name)
if f.type is int:
val = str(val)
data[f.name] = val
return data
# %%
addr = Address("Teststraße", 202, "09111", "Chemnitz")
# %%
addr.export()
# %%