""" views/admin_users_view.py — Admin panel: User Management tab. """ import tkinter as tk from tkinter import ttk import logging from utils.ui_helpers import ( COLOURS, FONT, FONT_BOLD, FONT_HEADING, show_error, show_info, confirm_delete ) logger = logging.getLogger("admin_users_view") class AdminUsersView(ttk.Frame): def __init__(self, parent, current_user: dict): super().__init__(parent) self.current_user = current_user self.configure(style="TFrame") self._selected_user_id = None self._build_ui() self._load_users() # ─── Layout ─────────────────────────────────────────────────────────────── def _build_ui(self): # Top toolbar toolbar = ttk.Frame(self) toolbar.pack(fill="x", pady=(0, 10)) ttk.Label(toolbar, text="User Management", style="Heading.TLabel").pack(side="left") ttk.Button(toolbar, text="+ Add User", command=self._open_add_dialog).pack(side="right", padx=(4, 0)) ttk.Button(toolbar, text="✎ Edit", style="Ghost.TButton", command=self._open_edit_dialog).pack(side="right", padx=(4, 0)) ttk.Button(toolbar, text="✕ Delete", style="Danger.TButton", command=self._delete_selected).pack(side="right") # Treeview cols = ("ID", "Username", "Full Name", "Role", "Active", "Created") self.tree = ttk.Treeview(self, columns=cols, show="headings", selectmode="browse") widths = [40, 140, 180, 80, 60, 160] for col, w in zip(cols, widths): self.tree.heading(col, text=col) self.tree.column(col, width=w, anchor="center" if w < 120 else "w") self.tree.pack(fill="both", expand=True) self.tree.bind("", lambda _: self._open_edit_dialog()) vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview) self.tree.configure(yscrollcommand=vsb.set) vsb.place(relx=1, rely=0, relheight=1, anchor="ne") # ─── Data ───────────────────────────────────────────────────────────────── def _load_users(self): from models import get_all_users self.tree.delete(*self.tree.get_children()) try: for u in get_all_users(): active = "✔" if u["is_active"] else "✘" created = str(u["created_at"])[:16] if u["created_at"] else "" self.tree.insert("", "end", iid=str(u["id"]), values=(u["id"], u["username"], u["full_name"] or "", u["role"], active, created)) except Exception as e: show_error(f"Failed to load users:\n{e}") def _get_selected_id(self): sel = self.tree.selection() return int(sel[0]) if sel else None # ─── Dialogs ────────────────────────────────────────────────────────────── def _open_add_dialog(self): UserDialog(self, self.current_user, user_data=None, on_save=self._load_users) def _open_edit_dialog(self): uid = self._get_selected_id() if not uid: show_error("Please select a user to edit.") return from models import get_user_by_id user_data = get_user_by_id(uid) UserDialog(self, self.current_user, user_data=user_data, on_save=self._load_users) def _delete_selected(self): uid = self._get_selected_id() if not uid: show_error("Please select a user to delete.") return if uid == self.current_user["id"]: show_error("You cannot delete your own account.") return vals = self.tree.item(uid, "values") username = vals[1] if vals else str(uid) if confirm_delete(username): try: from models import delete_user delete_user(self.current_user["id"], uid) logger.info(f"Admin {self.current_user['username']} deleted user id={uid}.") show_info(f"User '{username}' deleted successfully.") self._load_users() except Exception as e: show_error(f"Delete failed:\n{e}") # ─── User Dialog (Add / Edit) ───────────────────────────────────────────────── class UserDialog(tk.Toplevel): def __init__(self, parent, current_user, user_data, on_save): super().__init__(parent) self.current_user = current_user self.user_data = user_data self.on_save = on_save self.is_edit = user_data is not None self.title("Edit User" if self.is_edit else "Add User") self.resizable(False, False) self.configure(bg=COLOURS["bg"]) self.grab_set() self._build_ui() self._centre() def _centre(self): self.update_idletasks() w, h = 420, 420 x = (self.winfo_screenwidth() - w) // 2 y = (self.winfo_screenheight() - h) // 2 self.geometry(f"{w}x{h}+{x}+{y}") def _build_ui(self): pad = {"padx": 24, "pady": 8} ttk.Label(self, text="Edit User" if self.is_edit else "New User", style="Heading.TLabel").pack(anchor="w", padx=24, pady=(20, 4)) form = ttk.Frame(self) form.pack(fill="x", padx=24, pady=8) form.columnconfigure(1, weight=1) def row(label, row_n, show=None): ttk.Label(form, text=label).grid(row=row_n, column=0, sticky="w", padx=(0, 10), pady=6) var = tk.StringVar() ent = ttk.Entry(form, textvariable=var, show=show or "") ent.grid(row=row_n, column=1, sticky="ew", pady=6) return var, ent self.full_name_var, _ = row("Full Name", 0) self.username_var, _ = row("Username", 1) self.password_var, _ = row("Password", 2, show="•") pw_hint = "(leave blank to keep unchanged)" if self.is_edit else "" ttk.Label(form, text=pw_hint, style="Dim.TLabel").grid( row=3, column=1, sticky="w") ttk.Label(form, text="Role").grid(row=4, column=0, sticky="w", padx=(0, 10), pady=6) self.role_var = tk.StringVar(value="user") role_cb = ttk.Combobox(form, textvariable=self.role_var, values=["admin", "user"], state="readonly") role_cb.grid(row=4, column=1, sticky="ew", pady=6) ttk.Label(form, text="Active").grid(row=5, column=0, sticky="w", padx=(0, 10), pady=6) self.active_var = tk.BooleanVar(value=True) ttk.Checkbutton(form, variable=self.active_var).grid(row=5, column=1, sticky="w", pady=6) if self.is_edit: d = self.user_data self.full_name_var.set(d.get("full_name") or "") self.username_var.set(d.get("username") or "") self.role_var.set(d.get("role") or "user") self.active_var.set(bool(d.get("is_active", 1))) btn_frame = ttk.Frame(self) btn_frame.pack(fill="x", padx=24, pady=(16, 20)) ttk.Button(btn_frame, text="Save", command=self._save).pack(side="right", padx=(6, 0)) ttk.Button(btn_frame, text="Cancel", style="Ghost.TButton", command=self.destroy).pack(side="right") def _save(self): full_name = self.full_name_var.get().strip() username = self.username_var.get().strip() password = self.password_var.get() role = self.role_var.get() is_active = int(self.active_var.get()) if not username: show_error("Username is required.") return if not self.is_edit and not password: show_error("Password is required for new users.") return try: if self.is_edit: from models import update_user update_user(self.current_user["id"], self.user_data["id"], username, role, full_name, is_active, password if password else None) logger.info(f"User id={self.user_data['id']} updated by admin.") show_info("User updated successfully.") else: from models import create_user create_user(self.current_user["id"], username, password, role, full_name) logger.info(f"New user '{username}' created by admin.") show_info("User created successfully.") self.on_save() self.destroy() except Exception as e: show_error(f"Save failed:\n{e}")