mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-09-06 03:20:49 +02:00
Refine member and asset detail layouts
This commit is contained in:
+127
-22
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user