Update member UI and related app changes

This commit is contained in:
Marcel Peterkau
2026-06-26 21:57:11 +02:00
parent 833075f0dc
commit 30b6d253b2
18 changed files with 490 additions and 84 deletions
+118 -18
View File
@@ -5,6 +5,58 @@ from tkinter import messagebox, ttk
from ccma.domain.dates import format_date_for_display
from ccma.domain.models import MEMBERSHIP_STATUS_LABELS, HousekeeperFinding, Member
from ccma.ui.labels import storage_key
MEMBER_TABLE_COLUMNS = (
("number", "Nummer", 110),
("first_name", "Vorname", 160),
("last_name", "Nachname", 180),
("nickname", "Nickname", 160),
("email", "E-Mail-Adresse", 270),
("birth", "Geburtsdatum", 120),
("status", "Status", 170),
)
STATUS_FILTER_ALL = "Alle"
def _member_table_value(member: Member, column: str) -> str:
if column == "number":
return member.member_number
if column == "first_name":
return member.first_name
if column == "last_name":
return member.last_name
if column == "nickname":
return member.nickname
if column == "email":
return member.email
if column == "birth":
return member.birth_date
if column == "status":
return MEMBERSHIP_STATUS_LABELS.get(member.status, member.status)
return ""
def _filter_members(members: list[Member], status_filter: str) -> list[Member]:
if status_filter == "all":
return list(members)
return [member for member in members if member.status == status_filter]
def _sort_members(members: list[Member], column: str, descending: bool) -> list[Member]:
return sorted(
members,
key=lambda member: _member_table_value(member, column).casefold(),
reverse=descending,
)
def _selected_status_filter(label: str) -> str:
if label == STATUS_FILTER_ALL:
return "all"
return storage_key(MEMBERSHIP_STATUS_LABELS, label)
class DashboardTab(ttk.Frame):
@@ -82,11 +134,17 @@ class SearchResultsTab(ttk.Frame):
ttk.Label(header, text=f"{len(self.members)} Treffer", style="Mono.TLabel").grid(
row=1, column=0, sticky="w"
)
ttk.Button(header, text="Tab schließen", command=self.on_close).grid(row=0, column=1, rowspan=2)
tree = ttk.Treeview(self, columns=("number", "name", "email", "birth", "status"), show="headings")
ttk.Button(header, text="Tab schließen", command=self.on_close).grid(row=0, column=3, rowspan=2)
tree = ttk.Treeview(
self,
columns=("number", "first_name", "last_name", "nickname", "email", "birth", "status"),
show="headings",
)
for key, title, width in (
("number", "Nummer", 90),
("name", "Name", 220),
("first_name", "Vorname", 150),
("last_name", "Nachname", 170),
("nickname", "Nickname", 150),
("email", "E-Mail-Adresse", 260),
("birth", "Geburtsdatum", 110),
("status", "Status", 160),
@@ -101,7 +159,9 @@ class SearchResultsTab(ttk.Frame):
iid=member.member_id,
values=(
member.member_number,
member.display_name,
member.first_name,
member.last_name,
member.nickname,
member.email,
format_date_for_display(member.birth_date),
MEMBERSHIP_STATUS_LABELS.get(member.status, member.status),
@@ -132,7 +192,7 @@ class MembersTab(ttk.Frame):
def _build_ui(self) -> None:
self.columnconfigure(0, weight=1)
self.rowconfigure(1, weight=1)
self.rowconfigure(2, weight=1)
header = ttk.Frame(self)
header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
header.columnconfigure(0, weight=1)
@@ -140,41 +200,81 @@ class MembersTab(ttk.Frame):
self.count_var = tk.StringVar()
ttk.Label(header, textvariable=self.count_var, style="Mono.TLabel").grid(row=1, column=0, sticky="w")
ttk.Button(header, text="Tab schließen", command=self.on_close).grid(row=0, column=1, rowspan=2)
filters = ttk.Frame(self, padding=(12, 10))
filters.grid(row=1, column=0, sticky="ew", pady=(0, 10))
filters.columnconfigure(1, weight=1)
ttk.Label(filters, text="Filter", style="Mono.TLabel").grid(row=0, column=0, sticky="w", padx=(0, 16))
self.tree = ttk.Treeview(
self, columns=("number", "name", "email", "birth", "status"), show="headings"
self,
columns=("number", "first_name", "last_name", "nickname", "email", "birth", "status"),
show="headings",
)
for key, title, width in (
("number", "Nummer", 110),
("name", "Name", 230),
("email", "E-Mail-Adresse", 270),
("birth", "Geburtsdatum", 120),
("status", "Status", 170),
):
self.tree.heading(key, text=title)
self.sort_column = "last_name"
self.sort_descending = False
self.status_filter_var = tk.StringVar(value=STATUS_FILTER_ALL)
ttk.Label(filters, text="Status").grid(row=0, column=1, sticky="w", padx=(0, 8))
self.status_filter = ttk.Combobox(
filters,
textvariable=self.status_filter_var,
state="readonly",
values=[STATUS_FILTER_ALL, *MEMBERSHIP_STATUS_LABELS.values()],
width=28,
)
self.status_filter.grid(row=0, column=2, sticky="w")
self.status_filter.bind("<<ComboboxSelected>>", lambda _event: self._render_members())
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")
self.tree.grid(row=1, column=0, sticky="nsew")
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)
def refresh(self, members: list[Member]) -> None:
self.members = members
self._render_members()
def _render_members(self) -> None:
self.tree.delete(*self.tree.get_children())
self.count_var.set(f"{len(members)} Mitglieder")
for member in members:
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)
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:
self.tree.insert(
"",
"end",
iid=member.member_id,
values=(
member.member_number,
member.display_name,
member.first_name,
member.last_name,
member.nickname,
member.email,
format_date_for_display(member.birth_date),
MEMBERSHIP_STATUS_LABELS.get(member.status, member.status),
),
)
def _toggle_sort(self, column: str) -> None:
if self.sort_column == column:
self.sort_descending = not self.sort_descending
else:
self.sort_column = column
self.sort_descending = False
self._render_members()
def _update_tree_headings(self) -> None:
for key, title, _width in MEMBER_TABLE_COLUMNS:
suffix = ""
if key == self.sort_column:
suffix = " v" if self.sort_descending else " ^"
self.tree.heading(key, text=f"{title}{suffix}", command=lambda column=key: self._toggle_sort(column))
def _open_selected(self) -> None:
selected = self.tree.selection()
if selected: