From 3e9f34743528094e1165a4830793f2ccda9f3fb3 Mon Sep 17 00:00:00 2001 From: Marcel Peterkau Date: Sun, 21 Jun 2026 17:59:49 +0200 Subject: [PATCH] feat: show housekeeper task details --- src/ccma/assets/CHANGELOG.json | 1 + src/ccma/ui/work_tabs.py | 38 ++++++++++++++++++++++++++++++++++ tests/test_ui_imports.py | 21 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/src/ccma/assets/CHANGELOG.json b/src/ccma/assets/CHANGELOG.json index 5c99e61..5d72e27 100644 --- a/src/ccma/assets/CHANGELOG.json +++ b/src/ccma/assets/CHANGELOG.json @@ -21,6 +21,7 @@ "Regelskripte im Mitglieder-Store können eingebaute Regeln anhand ihres Dateinamens gezielt ersetzen.", "Beschädigte Beitragsdateien blockieren die Mitgliederansicht nicht mehr und werden vom Hausmeister ohne automatisches Überschreiben gemeldet.", "Ein Akten-Preflight sperrt bei beschädigten Mitglieder-, Beitrags- oder Eventdateien alle Regeln für die betroffene Akte.", + "Der Hausmeister-Tab zeigt den vollständigen Inhalt eines markierten Vorgangs in einem mehrzeiligen Detailbereich.", "Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.", "Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.", "Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.", diff --git a/src/ccma/ui/work_tabs.py b/src/ccma/ui/work_tabs.py index 321e5e9..82a4455 100644 --- a/src/ccma/ui/work_tabs.py +++ b/src/ccma/ui/work_tabs.py @@ -227,6 +227,24 @@ class HousekeeperTab(ttk.Frame): self.tree.column(key, width=width, anchor="w") self.tree.grid(row=1, column=0, sticky="nsew") self.tree.bind("", lambda _event: self._open_selected()) + self.tree.bind("<>", lambda _event: self._show_selected_details()) + details = ttk.LabelFrame(self, text="Details", padding=12) + details.grid(row=2, column=0, sticky="ew", pady=(10, 0)) + details.columnconfigure(0, weight=1) + self.detail_var = tk.StringVar(value="Eintrag auswählen, um Details anzuzeigen.") + self.detail_label = ttk.Label( + details, + textvariable=self.detail_var, + anchor="nw", + justify="left", + wraplength=800, + style="Status.TLabel", + ) + self.detail_label.grid(row=0, column=0, sticky="ew") + details.bind( + "", + lambda event: self.detail_label.configure(wraplength=max(300, event.width - 32)), + ) self._render() def refresh(self) -> None: @@ -235,6 +253,7 @@ class HousekeeperTab(ttk.Frame): def _render(self) -> None: self.tree.delete(*self.tree.get_children()) + self.detail_var.set("Eintrag auswählen, um Details anzuzeigen.") self.title_var.set(f"HAUSMEISTER · {len(self.findings)} Vorgänge") for index, finding in enumerate(self.findings): self.tree.insert( @@ -244,7 +263,26 @@ class HousekeeperTab(ttk.Frame): values=(finding.severity.upper(), finding.title, finding.detail, finding.due_date or ""), ) + def _show_selected_details(self) -> None: + selected = self.tree.selection() + if not selected: + self.detail_var.set("Eintrag auswählen, um Details anzuzeigen.") + return + index = int(selected[0]) + if index >= len(self.findings): + return + self.detail_var.set(_finding_details(self.findings[index])) + def _open_selected(self) -> None: selected = self.tree.selection() if selected: self.on_open_member(self.findings[int(selected[0])].member_id) + + +def _finding_details(finding: HousekeeperFinding) -> str: + lines = [f"{finding.severity.upper()} · {finding.code}", finding.title] + if finding.due_date: + lines.append(f"Fällig: {format_date_for_display(finding.due_date.isoformat())}") + if finding.detail: + lines.extend(("", finding.detail)) + return "\n".join(lines) diff --git a/tests/test_ui_imports.py b/tests/test_ui_imports.py index e08d5f8..e522e5e 100644 --- a/tests/test_ui_imports.py +++ b/tests/test_ui_imports.py @@ -38,3 +38,24 @@ def test_event_labels_hide_board_actor_but_keep_automatic_marker() -> None: system_event = Event("2", "2026-01-01T00:00:00+01:00", "automatic", "Automatisch") assert _event_label(user_event) == "Kommentar" assert _event_label(system_event) == "[AUTO] Automatisch" + + +def test_housekeeper_details_are_multiline() -> None: + from datetime import date + + from ccma.domain.models import HousekeeperFinding + from ccma.ui.work_tabs import _finding_details + + finding = HousekeeperFinding( + severity="error", + member_id="member-1", + code="invalid_member_record", + title="Mitgliederakte beschädigt", + detail="Die JSON-Datei ist leer und wird nicht automatisch überschrieben.", + due_date=date(2026, 7, 31), + ) + + rendered = _finding_details(finding) + assert rendered.splitlines()[0] == "ERROR · invalid_member_record" + assert "Mitgliederakte beschädigt\nFällig:" in rendered + assert rendered.endswith("nicht automatisch überschrieben.")