mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-02 11:40:13 +02:00
feat: initialize CCMA member administration
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import tkinter as tk
|
||||
from collections import Counter
|
||||
from collections.abc import Callable
|
||||
from tkinter import ttk
|
||||
|
||||
from ccma.domain.dates import format_date_for_display
|
||||
from ccma.domain.models import MEMBERSHIP_STATUS_LABELS, HousekeeperFinding, Member
|
||||
|
||||
|
||||
class DashboardTab(ttk.Frame):
|
||||
def __init__(
|
||||
self,
|
||||
master: tk.Misc,
|
||||
member_count: int,
|
||||
findings: list[HousekeeperFinding],
|
||||
on_housekeeper: Callable[[], None],
|
||||
):
|
||||
super().__init__(master, padding=24)
|
||||
self.member_count = member_count
|
||||
self.findings = findings
|
||||
self.on_housekeeper = on_housekeeper
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
ttk.Label(self, text="SYSTEM OVERVIEW", style="TabTitle.TLabel").grid(row=0, column=0, sticky="w")
|
||||
ttk.Label(self, text="Mitgliederverwaltung · lokaler File-Store", style="Mono.TLabel").grid(
|
||||
row=1, column=0, sticky="w", pady=(3, 22)
|
||||
)
|
||||
cards = ttk.Frame(self)
|
||||
cards.grid(row=2, column=0, sticky="ew")
|
||||
counts = Counter(finding.severity for finding in self.findings)
|
||||
values = [
|
||||
("MITGLIEDER", str(self.member_count), ""),
|
||||
("ACTION REQUIRED", str(counts["error"]), "CardError.TLabel"),
|
||||
("DUE SOON", str(counts["warning"] + counts["info"]), "CardWarning.TLabel"),
|
||||
("DATA INTEGRITY", "OK", ""),
|
||||
]
|
||||
for column, (label, value, style) in enumerate(values):
|
||||
card = ttk.Frame(cards, style="Card.TFrame", padding=18)
|
||||
card.grid(row=0, column=column, sticky="nsew", padx=(0, 10))
|
||||
cards.columnconfigure(column, weight=1)
|
||||
ttk.Label(card, text=label, style="CardTitle.TLabel").pack(anchor="w")
|
||||
ttk.Label(card, text=value, style=style or "CardValue.TLabel").pack(anchor="w", pady=(8, 0))
|
||||
ttk.Button(self, text="Hausmeister öffnen", style="Accent.TButton", command=self.on_housekeeper).grid(
|
||||
row=3, column=0, sticky="w", pady=(24, 0)
|
||||
)
|
||||
|
||||
def update_data(self, member_count: int, findings: list[HousekeeperFinding]) -> None:
|
||||
self.member_count = member_count
|
||||
self.findings = findings
|
||||
for child in self.winfo_children():
|
||||
child.destroy()
|
||||
self._build_ui()
|
||||
|
||||
|
||||
class SearchResultsTab(ttk.Frame):
|
||||
def __init__(
|
||||
self,
|
||||
master: tk.Misc,
|
||||
query: str,
|
||||
members: list[Member],
|
||||
on_open: Callable[[str], None],
|
||||
on_close: Callable[[], None],
|
||||
):
|
||||
super().__init__(master, padding=12)
|
||||
self.query = query
|
||||
self.members = members
|
||||
self.on_open = on_open
|
||||
self.on_close = on_close
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(1, weight=1)
|
||||
header = ttk.Frame(self)
|
||||
header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
||||
header.columnconfigure(0, weight=1)
|
||||
ttk.Label(header, text=f'Suche: "{self.query}"', style="TabTitle.TLabel").grid(
|
||||
row=0, column=0, sticky="w"
|
||||
)
|
||||
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")
|
||||
for key, title, width in (
|
||||
("number", "Nummer", 90),
|
||||
("name", "Name", 220),
|
||||
("email", "E-Mail-Adresse", 260),
|
||||
("birth", "Geburtsdatum", 110),
|
||||
("status", "Status", 160),
|
||||
):
|
||||
tree.heading(key, text=title)
|
||||
tree.column(key, width=width, anchor="w")
|
||||
tree.grid(row=1, column=0, sticky="nsew")
|
||||
for member in self.members:
|
||||
tree.insert(
|
||||
"",
|
||||
"end",
|
||||
iid=member.member_id,
|
||||
values=(
|
||||
member.member_number,
|
||||
member.display_name,
|
||||
member.email,
|
||||
format_date_for_display(member.birth_date),
|
||||
MEMBERSHIP_STATUS_LABELS.get(member.status, member.status),
|
||||
),
|
||||
)
|
||||
tree.bind("<Double-1>", lambda _event: self._open_selected(tree))
|
||||
tree.bind("<Return>", lambda _event: self._open_selected(tree))
|
||||
|
||||
def _open_selected(self, tree: ttk.Treeview) -> None:
|
||||
selected = tree.selection()
|
||||
if selected:
|
||||
self.on_open(selected[0])
|
||||
|
||||
|
||||
class MembersTab(ttk.Frame):
|
||||
def __init__(
|
||||
self,
|
||||
master: tk.Misc,
|
||||
members: list[Member],
|
||||
on_open: Callable[[str], None],
|
||||
on_close: Callable[[], None],
|
||||
):
|
||||
super().__init__(master, padding=12)
|
||||
self.members = members
|
||||
self.on_open = on_open
|
||||
self.on_close = on_close
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(1, weight=1)
|
||||
header = ttk.Frame(self)
|
||||
header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
||||
header.columnconfigure(0, weight=1)
|
||||
ttk.Label(header, text="MITGLIEDER", style="TabTitle.TLabel").grid(row=0, column=0, sticky="w")
|
||||
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)
|
||||
self.tree = ttk.Treeview(
|
||||
self, columns=("number", "name", "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.tree.column(key, width=width, anchor="w")
|
||||
self.tree.grid(row=1, 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.tree.delete(*self.tree.get_children())
|
||||
self.count_var.set(f"{len(members)} Mitglieder")
|
||||
for member in members:
|
||||
self.tree.insert(
|
||||
"",
|
||||
"end",
|
||||
iid=member.member_id,
|
||||
values=(
|
||||
member.member_number,
|
||||
member.display_name,
|
||||
member.email,
|
||||
format_date_for_display(member.birth_date),
|
||||
MEMBERSHIP_STATUS_LABELS.get(member.status, member.status),
|
||||
),
|
||||
)
|
||||
|
||||
def _open_selected(self) -> None:
|
||||
selected = self.tree.selection()
|
||||
if selected:
|
||||
self.on_open(selected[0])
|
||||
|
||||
|
||||
class HousekeeperTab(ttk.Frame):
|
||||
def __init__(
|
||||
self,
|
||||
master: tk.Misc,
|
||||
findings: list[HousekeeperFinding],
|
||||
on_open_member: Callable[[str], None],
|
||||
on_refresh: Callable[[], list[HousekeeperFinding]],
|
||||
on_close: Callable[[], None],
|
||||
):
|
||||
super().__init__(master, padding=12)
|
||||
self.findings = findings
|
||||
self.on_open_member = on_open_member
|
||||
self.on_refresh = on_refresh
|
||||
self.on_close = on_close
|
||||
self._build_ui()
|
||||
|
||||
def _build_ui(self) -> None:
|
||||
self.columnconfigure(0, weight=1)
|
||||
self.rowconfigure(1, weight=1)
|
||||
header = ttk.Frame(self)
|
||||
header.grid(row=0, column=0, sticky="ew", pady=(0, 10))
|
||||
header.columnconfigure(0, weight=1)
|
||||
self.title_var = tk.StringVar()
|
||||
ttk.Label(header, textvariable=self.title_var, style="TabTitle.TLabel").grid(
|
||||
row=0, column=0, sticky="w"
|
||||
)
|
||||
ttk.Label(
|
||||
header, text="Prüfend, keine Aktionen werden automatisch ausgeführt", style="Mono.TLabel"
|
||||
).grid(row=1, column=0, sticky="w")
|
||||
ttk.Button(header, text="Neu prüfen", command=self.refresh).grid(
|
||||
row=0, column=1, rowspan=2, padx=(0, 8)
|
||||
)
|
||||
ttk.Button(header, text="Tab schließen", command=self.on_close).grid(row=0, column=2, rowspan=2)
|
||||
self.tree = ttk.Treeview(self, columns=("severity", "title", "detail", "due"), show="headings")
|
||||
for key, title, width in (
|
||||
("severity", "Level", 90),
|
||||
("title", "Vorgang", 330),
|
||||
("detail", "Details", 390),
|
||||
("due", "Fällig", 110),
|
||||
):
|
||||
self.tree.heading(key, text=title)
|
||||
self.tree.column(key, width=width, anchor="w")
|
||||
self.tree.grid(row=1, column=0, sticky="nsew")
|
||||
self.tree.bind("<Double-1>", lambda _event: self._open_selected())
|
||||
self._render()
|
||||
|
||||
def refresh(self) -> None:
|
||||
self.findings = self.on_refresh()
|
||||
self._render()
|
||||
|
||||
def _render(self) -> None:
|
||||
self.tree.delete(*self.tree.get_children())
|
||||
self.title_var.set(f"HAUSMEISTER · {len(self.findings)} Vorgänge")
|
||||
for index, finding in enumerate(self.findings):
|
||||
self.tree.insert(
|
||||
"",
|
||||
"end",
|
||||
iid=str(index),
|
||||
values=(finding.severity.upper(), finding.title, finding.detail, finding.due_date or ""),
|
||||
)
|
||||
|
||||
def _open_selected(self) -> None:
|
||||
selected = self.tree.selection()
|
||||
if selected:
|
||||
self.on_open_member(self.findings[int(selected[0])].member_id)
|
||||
Reference in New Issue
Block a user