mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-08-24 14:35:19 +02:00
fix: improve member list defaults and highlighting
This commit is contained in:
@@ -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."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -131,6 +131,20 @@ def test_member_table_sort_uses_display_values() -> None:
|
||||
assert [member.member_id for member in _sort_members(members, "status", False)] == ["2", "1", "3"]
|
||||
|
||||
|
||||
def test_member_finding_severity_uses_highest_level_per_member() -> None:
|
||||
from ccma.domain.models import HousekeeperFinding
|
||||
from ccma.ui.work_tabs import _member_finding_severities
|
||||
|
||||
findings = [
|
||||
HousekeeperFinding("info", "info", "Info", "", member_id="1"),
|
||||
HousekeeperFinding("warning", "warning", "Warning", "", member_id="1"),
|
||||
HousekeeperFinding("error", "error", "Error", "", member_id="2"),
|
||||
HousekeeperFinding("info", "global", "Global", ""),
|
||||
]
|
||||
|
||||
assert _member_finding_severities(findings) == {"1": "warning", "2": "error"}
|
||||
|
||||
|
||||
def test_asset_table_filter_and_sort_use_status_and_holder_label() -> None:
|
||||
from ccma.domain.models import Asset
|
||||
from ccma.ui.work_tabs import _asset_table_value, _filter_assets, _selected_asset_filter
|
||||
|
||||
Reference in New Issue
Block a user