04/21 Fisrt commit

This commit is contained in:
2026-04-21 17:16:37 -04:00
parent f94d86f04b
commit 7548dfc9bf
23 changed files with 6860 additions and 1 deletions
+219
View File
@@ -0,0 +1,219 @@
"""
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("<Double-1>", 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}")