Refine member and asset detail layouts

This commit is contained in:
Marcel Peterkau
2026-06-27 10:36:03 +02:00
parent 87e972bb43
commit 04c23fbdf9
5 changed files with 452 additions and 52 deletions
+127 -22
View File
@@ -7,7 +7,9 @@ from tkinter import messagebox, ttk
from ccma.domain.models import ASSET_STATUS_LABELS, Asset, Event
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.dialogs import AssetClaimDialog
from ccma.ui.dialogs import AssetClaimDialog, IntegrityWarningDialog
from ccma.ui.messages import MessageAction, MessageBannerList, TabMessage
from ccma.ui.scrolling import ScrollableFrame
class AssetTab(ttk.Frame):
@@ -40,18 +42,35 @@ class AssetTab(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)
header.columnconfigure(1, weight=0)
self.title_var = tk.StringVar()
self.subtitle_var = tk.StringVar()
ttk.Label(header, textvariable=self.title_var, style="TabTitle.TLabel").grid(row=0, column=0, sticky="w")
ttk.Label(header, textvariable=self.subtitle_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, sticky="e")
self.id_var = tk.StringVar()
title_column = ttk.Frame(header)
title_column.grid(row=0, column=0, sticky="ew")
title_column.columnconfigure(0, weight=1)
ttk.Label(title_column, textvariable=self.title_var, style="TabTitle.TLabel").grid(
row=0, column=0, sticky="w"
)
ttk.Label(title_column, textvariable=self.subtitle_var, style="Mono.TLabel").grid(
row=1, column=0, sticky="w"
)
ttk.Label(title_column, textvariable=self.id_var, style="Mono.TLabel").grid(
row=2, column=0, sticky="w", pady=(3, 0)
)
ttk.Button(header, text="Tab schließen", command=self.on_close).grid(
row=0, column=1, sticky="ne", padx=(12, 0)
)
self.messages = MessageBannerList(self)
self.messages.grid(row=1, column=0, sticky="ew", pady=(0, 10))
self.messages.grid_remove()
self.pane = ttk.Panedwindow(self, orient="horizontal")
self.pane.grid(row=1, column=0, sticky="nsew")
self.pane.grid(row=2, column=0, sticky="nsew")
self.details_pane = ttk.Frame(self.pane, padding=(0, 0, 10, 0))
self.timeline_pane = ttk.Frame(self.pane, padding=(10, 0, 0, 0))
self.pane.add(self.details_pane, weight=2)
@@ -74,15 +93,23 @@ class AssetTab(ttk.Frame):
def _build_details(self, parent: ttk.Frame) -> None:
parent.columnconfigure(0, weight=1)
parent.rowconfigure(0, weight=1)
parent.rowconfigure(1, weight=1)
ttk.Label(parent, text="// ASSET", style="TimelineHeader.TLabel").grid(
row=0, column=0, sticky="w", pady=(0, 8)
)
notebook = ttk.Notebook(parent)
notebook.grid(row=0, column=0, sticky="nsew")
data_tab = ttk.Frame(notebook, padding=16)
notebook.grid(row=1, column=0, sticky="nsew")
data_tab = self._create_form_tab(notebook, "Stammdaten")
actions = ttk.Frame(parent)
actions.grid(row=2, column=0, sticky="ew", pady=(10, 0))
actions.columnconfigure(0, weight=1)
action_buttons = ttk.Frame(actions)
action_buttons.grid(row=0, column=0, sticky="e")
finance_tab = ttk.Frame(notebook, padding=16)
notebook.add(data_tab, text="Stammdaten")
notebook.add(finance_tab, text="Forderungen")
fields = [
("UUID / Ordner-ID", "asset_id"),
("Bezeichnung", "label"),
("Kategorie", "category"),
("Inventarnummer", "inventory_number"),
@@ -94,6 +121,8 @@ class AssetTab(ttk.Frame):
ttk.Label(data_tab, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
entry = ttk.Entry(data_tab, textvariable=self.variables[key], width=42)
entry.grid(row=row, column=1, sticky="ew", pady=5)
if key == "asset_id":
entry.configure(state="readonly")
if key == "deposit_amount_default":
self.deposit_entry = entry
self.variables["status"] = tk.StringVar()
@@ -108,23 +137,48 @@ class AssetTab(ttk.Frame):
)
self.status_box.grid(row=holder_row, column=1, sticky="ew", pady=5)
self.holder_var = tk.StringVar()
ttk.Label(data_tab, text="Aktueller Halter").grid(row=holder_row + 1, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(data_tab, textvariable=self.holder_var, style="TimelineHeader.TLabel").grid(
row=holder_row + 1, column=1, sticky="w", pady=5
ttk.Label(data_tab, text="Aktueller Halter").grid(
row=holder_row + 1,
column=0,
sticky="w",
pady=5,
padx=(0, 12),
)
self.holder_label = ttk.Label(
data_tab,
textvariable=self.holder_var,
style="TimelineHeader.TLabel",
)
self.holder_label.grid(row=holder_row + 1, column=1, sticky="w", pady=5)
self.holder_label.bind("<Button-1>", lambda _event: self._open_holder_member(), add="+")
ttk.Label(data_tab, text="Interne Notiz").grid(
row=holder_row + 2,
column=0,
sticky="nw",
pady=5,
padx=(0, 12),
)
ttk.Label(data_tab, text="Interne Notiz").grid(row=holder_row + 2, column=0, sticky="nw", pady=5, padx=(0, 12))
self.notes_text = tk.Text(data_tab, width=42, height=6, wrap="word")
self.notes_text.grid(row=holder_row + 2, column=1, sticky="ew", pady=5)
data_tab.columnconfigure(1, weight=1)
actions = ttk.Frame(data_tab)
actions.grid(row=holder_row + 3, column=1, sticky="e", pady=(16, 0))
self.open_member_button = ttk.Button(actions, text="Mitglied öffnen", command=self._open_holder_member)
self.open_member_button.pack(side="left", padx=(0, 8))
self.issue_button = ttk.Button(actions, text="Ausgeben", command=lambda: self.on_issue_asset(self.asset_id))
self.issue_button = ttk.Button(
action_buttons,
text="Ausgeben",
command=lambda: self.on_issue_asset(self.asset_id),
)
self.issue_button.pack(side="left", padx=(0, 8))
self.return_button = ttk.Button(actions, text="Zurücknehmen", command=lambda: self.on_return_asset(self.asset_id))
self.return_button = ttk.Button(
action_buttons,
text="Zurücknehmen",
command=lambda: self.on_return_asset(self.asset_id),
)
self.return_button.pack(side="left", padx=(0, 8))
ttk.Button(actions, text="Speichern", style="Accent.TButton", command=self._save).pack(side="left")
ttk.Button(
action_buttons,
text="Speichern",
style="Accent.TButton",
command=self._save,
).pack(side="left")
finance_tab.columnconfigure(0, weight=1)
self.finance_summary_var = tk.StringVar()
@@ -157,6 +211,15 @@ class AssetTab(ttk.Frame):
self.asset_claims.bind("<Double-1>", lambda _event: self._open_selected_asset_claim())
self.asset_claims.bind("<Return>", lambda _event: self._open_selected_asset_claim())
def _create_form_tab(self, notebook: ttk.Notebook, title: str) -> ttk.Frame:
tab = ttk.Frame(notebook)
tab.columnconfigure(0, weight=1)
tab.rowconfigure(0, weight=1)
scroller = ScrollableFrame(tab, padding=16)
scroller.grid(row=0, column=0, sticky="nsew")
notebook.add(tab, text=title)
return scroller.content
def _build_timeline(self, parent: ttk.Frame) -> None:
parent.columnconfigure(0, weight=1)
parent.rowconfigure(1, weight=1)
@@ -186,6 +249,20 @@ class AssetTab(ttk.Frame):
holder = self._holder_label()
status = ASSET_STATUS_LABELS.get(self.asset.status, self.asset.status.upper())
self.subtitle_var.set(f"{self.asset.inventory_number or ''} · {status} · {holder}")
self.id_var.set(f"UUID: {self.asset.asset_id}")
warnings = self.repository.asset_hash_warnings(self.asset_id)
self.messages.set_messages(
[
TabMessage(
"warning",
"WARNUNG: " + " | ".join(warnings),
MessageAction("Überprüft, bestätigen", self._confirm_integrity_banner),
)
]
if warnings
else []
)
self.variables["asset_id"].set(self.asset.asset_id)
self.variables["label"].set(self.asset.label)
self.variables["category"].set(self.asset.category)
self.variables["inventory_number"].set(self.asset.inventory_number)
@@ -197,7 +274,7 @@ class AssetTab(ttk.Frame):
self.notes_text.insert("1.0", self.asset.notes)
self.holder_var.set(holder)
issued = bool(self.asset.current_holder_member_id)
self.open_member_button.configure(state="normal" if issued else "disabled")
self.holder_label.configure(cursor="hand2" if issued else "")
self.issue_button.configure(state="normal" if self.asset.status == "available" else "disabled")
self.return_button.configure(state="normal" if issued else "disabled")
self.status_box.configure(state="disabled" if issued else "readonly")
@@ -255,6 +332,13 @@ class AssetTab(ttk.Frame):
)
def _save(self) -> None:
warnings = self.repository.asset_hash_warnings(self.asset_id)
if warnings:
self._confirm_integrity_and_then(self._save_confirmed)
return
self._save_confirmed()
def _save_confirmed(self) -> None:
self.asset.label = self.variables["label"].get().strip()
self.asset.category = self.variables["category"].get().strip()
self.asset.inventory_number = self.variables["inventory_number"].get().strip()
@@ -265,12 +349,33 @@ class AssetTab(ttk.Frame):
self.asset.status = _asset_status_key(self.variables["status"].get())
try:
self.repository.save_asset(self.asset)
self.repository.refresh_asset_record_hashes(self.asset_id)
except RepositoryError as exc:
messagebox.showerror("Asset konnte nicht gespeichert werden", str(exc), parent=self)
return
self.refresh()
self.on_changed()
def _confirm_integrity_banner(self) -> None:
self._confirm_integrity_and_then(self._refresh_hashes_only)
def _refresh_hashes_only(self) -> None:
self.repository.refresh_asset_record_hashes(self.asset_id)
self.refresh()
self.on_changed()
def _confirm_integrity_and_then(self, callback: Callable[[], None]) -> None:
warnings = self.repository.asset_hash_warnings(self.asset_id)
if not warnings:
callback()
return
IntegrityWarningDialog(
self,
title="Externe Änderungen bestätigen",
warnings=warnings,
on_confirm=callback,
)
def _add_comment(self) -> None:
text = self.comment_var.get().strip()
if not text: