fix: improve member list defaults and highlighting

This commit is contained in:
Marcel Peterkau
2026-07-24 18:14:49 +02:00
parent 68c24219ec
commit 9942e7aa22
4 changed files with 50 additions and 8 deletions
+3 -1
View File
@@ -9,7 +9,9 @@
"Forderungen können nachträglich bearbeitet werden, ohne zusätzliche Forderungspositionen oder bereits verbuchte Zahlungen zu verlieren.",
"Zahlungen können bearbeitet, neu auf mehrere Forderungen verteilt oder einschließlich ihrer Zuordnungen gelöscht werden.",
"Das Austrittsdatum eines Mitglieds wird gespeichert und validiert; automatische Beitragsforderungen werden nicht mehr für Fälligkeiten nach dem Mitgliedschaftsende erzeugt.",
"Anteilig berechnete Mitgliedsbeiträge weisen den abgerechneten Zeitraum verständlicher in ihrer Beschreibung aus."
"Anteilig berechnete Mitgliedsbeiträge weisen den abgerechneten Zeitraum verständlicher in ihrer Beschreibung aus.",
"Die Mitgliederliste zeigt standardmäßig aktive Mitglieder, ordnet Nachname vor Vorname an und sortiert aufsteigend nach Nachnamen.",
"Mitglieder mit offenen Hausmeister-Vorgängen werden in der Liste je nach höchster Dringlichkeit blau, gelb oder rot hervorgehoben."
]
},
{
+2 -1
View File
@@ -368,6 +368,7 @@ class MainWindow(ttk.Frame):
tab = MembersTab(
self.notebook,
self.repository.list_members(),
self.findings,
self.open_member,
lambda: self.tabs.close(key),
)
@@ -502,7 +503,7 @@ class MainWindow(ttk.Frame):
self.dashboard.update_data(self.repository.member_count(), self.findings)
members_tab = self.tabs.tabs.get("members")
if isinstance(members_tab, MembersTab) and members_tab.winfo_exists():
members_tab.refresh(self.repository.list_members())
members_tab.refresh(self.repository.list_members(), self.findings)
assets_tab = self.tabs.tabs.get("assets")
if isinstance(assets_tab, AssetsTab) and assets_tab.winfo_exists():
assets_tab.refresh(self.repository.list_assets())
+31 -6
View File
@@ -16,8 +16,8 @@ from ccma.ui.sections import titled_frame
MEMBER_TABLE_COLUMNS = (
("number", "Nummer", 110),
("first_name", "Vorname", 160),
("last_name", "Nachname", 180),
("first_name", "Vorname", 160),
("nickname", "Nickname", 160),
("email", "E-Mail-Adresse", 270),
("birth", "Geburtsdatum", 120),
@@ -25,6 +25,12 @@ MEMBER_TABLE_COLUMNS = (
)
STATUS_FILTER_ALL = "Alle"
MEMBER_FINDING_COLORS = {
"info": "#d9efff",
"warning": "#fff3b0",
"error": "#ffd0d0",
}
MEMBER_FINDING_PRIORITY = {"info": 1, "warning": 2, "error": 3}
ASSET_FILTER_ALL = "Alle"
ASSET_TABLE_COLUMNS = (
("label", "Bezeichnung", 260),
@@ -75,6 +81,17 @@ def _selected_status_filter(label: str) -> str:
return storage_key(MEMBERSHIP_STATUS_LABELS, label)
def _member_finding_severities(findings: list[HousekeeperFinding]) -> dict[str, str]:
severities: dict[str, str] = {}
for finding in findings:
if not finding.member_id or finding.severity not in MEMBER_FINDING_PRIORITY:
continue
current = severities.get(finding.member_id)
if current is None or MEMBER_FINDING_PRIORITY[finding.severity] > MEMBER_FINDING_PRIORITY[current]:
severities[finding.member_id] = finding.severity
return severities
def _filter_label_frame(parent: tk.Misc) -> ttk.LabelFrame:
return titled_frame(parent, "// FILTER")
@@ -225,11 +242,13 @@ class MembersTab(ttk.Frame):
self,
master: tk.Misc,
members: list[Member],
findings: list[HousekeeperFinding],
on_open: Callable[[str], None],
on_close: Callable[[], None],
):
super().__init__(master, padding=12)
self.members = members
self.findings = findings
self.on_open = on_open
self.on_close = on_close
self._build_ui()
@@ -250,12 +269,12 @@ class MembersTab(ttk.Frame):
filter_row.grid(row=0, column=0, sticky="w")
self.tree = ttk.Treeview(
self,
columns=("number", "first_name", "last_name", "nickname", "email", "birth", "status"),
columns=tuple(column for column, _title, _width in MEMBER_TABLE_COLUMNS),
show="headings",
)
self.sort_column = "last_name"
self.sort_descending = False
self.status_filter_var = tk.StringVar(value=STATUS_FILTER_ALL)
self.status_filter_var = tk.StringVar(value=MEMBERSHIP_STATUS_LABELS["active"])
ttk.Label(filter_row, text="Status").grid(row=0, column=0, sticky="w", padx=(0, 8))
self.status_filter = ttk.Combobox(
filter_row,
@@ -269,13 +288,16 @@ class MembersTab(ttk.Frame):
for key, title, width in MEMBER_TABLE_COLUMNS:
self.tree.heading(key, text=title, command=lambda column=key: self._toggle_sort(column))
self.tree.column(key, width=width, anchor="w")
for severity, color in MEMBER_FINDING_COLORS.items():
self.tree.tag_configure(f"finding-{severity}", background=color, foreground="#202020")
self.tree.grid(row=2, column=0, sticky="nsew")
self.tree.bind("<Double-1>", lambda _event: self._open_selected())
self.tree.bind("<Return>", lambda _event: self._open_selected())
self.refresh(self.members)
self.refresh(self.members, self.findings)
def refresh(self, members: list[Member]) -> None:
def refresh(self, members: list[Member], findings: list[HousekeeperFinding]) -> None:
self.members = members
self.findings = findings
self._render_members()
def _render_members(self) -> None:
@@ -283,20 +305,23 @@ class MembersTab(ttk.Frame):
status_filter = _selected_status_filter(self.status_filter_var.get())
filtered_members = _filter_members(self.members, status_filter)
sorted_members = _sort_members(filtered_members, self.sort_column, self.sort_descending)
finding_severities = _member_finding_severities(self.findings)
if len(filtered_members) == len(self.members):
self.count_var.set(f"{len(filtered_members)} Mitglieder")
else:
self.count_var.set(f"{len(filtered_members)} / {len(self.members)} Mitglieder")
self._update_tree_headings()
for member in sorted_members:
severity = finding_severities.get(member.member_id)
self.tree.insert(
"",
"end",
iid=member.member_id,
tags=(f"finding-{severity}",) if severity else (),
values=(
member.member_number,
member.first_name,
member.last_name,
member.first_name,
member.nickname,
member.email,
format_date_for_display(member.birth_date),