""" Medicine management window for adding, editing, and removing medicines. """ import tkinter as tk from tkinter import messagebox, ttk from medicine_manager import Medicine, MedicineManager class MedicineManagementWindow: """Window for managing medicine configurations.""" def __init__( self, parent: tk.Tk, medicine_manager: MedicineManager, refresh_callback ): self.parent = parent self.medicine_manager = medicine_manager self.refresh_callback = refresh_callback # Create the window self.window = tk.Toplevel(parent) self.window.title("Manage Medicines") self.window.geometry("600x500") self.window.resizable(True, True) # Make window modal self.window.transient(parent) self.window.grab_set() self._setup_ui() self._populate_medicine_list() # Center window self.window.update_idletasks() x = (self.window.winfo_screenwidth() // 2) - (600 // 2) y = (self.window.winfo_screenheight() // 2) - (500 // 2) self.window.geometry(f"600x500+{x}+{y}") def _setup_ui(self): """Set up the user interface.""" main_frame = ttk.Frame(self.window, padding="10") main_frame.grid(row=0, column=0, sticky="nsew") self.window.grid_rowconfigure(0, weight=1) self.window.grid_columnconfigure(0, weight=1) main_frame.grid_rowconfigure(1, weight=1) main_frame.grid_columnconfigure(0, weight=1) # Title title_label = ttk.Label( main_frame, text="Medicine Management", font=("Arial", 14, "bold") ) title_label.grid(row=0, column=0, columnspan=2, pady=(0, 10)) # Medicine list list_frame = ttk.LabelFrame(main_frame, text="Current Medicines") list_frame.grid(row=1, column=0, columnspan=2, sticky="nsew", pady=(0, 10)) list_frame.grid_rowconfigure(0, weight=1) list_frame.grid_columnconfigure(0, weight=1) # Treeview for medicines columns = ("key", "name", "dosage", "quick_doses", "color", "default") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings") # Column headings self.tree.heading("key", text="Key") self.tree.heading("name", text="Name") self.tree.heading("dosage", text="Dosage Info") self.tree.heading("quick_doses", text="Quick Doses") self.tree.heading("color", text="Color") self.tree.heading("default", text="Default Enabled") # Column widths self.tree.column("key", width=80) self.tree.column("name", width=100) self.tree.column("dosage", width=100) self.tree.column("quick_doses", width=120) self.tree.column("color", width=70) self.tree.column("default", width=100) self.tree.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # Scrollbar for treeview scrollbar = ttk.Scrollbar( list_frame, orient="vertical", command=self.tree.yview ) scrollbar.grid(row=0, column=1, sticky="ns") self.tree.configure(yscrollcommand=scrollbar.set) # Buttons button_frame = ttk.Frame(main_frame) button_frame.grid(row=2, column=0, columnspan=2, pady=(10, 0)) ttk.Button(button_frame, text="Add Medicine", command=self._add_medicine).grid( row=0, column=0, padx=(0, 5) ) ttk.Button( button_frame, text="Edit Medicine", command=self._edit_medicine ).grid(row=0, column=1, padx=5) ttk.Button( button_frame, text="Remove Medicine", command=self._remove_medicine ).grid(row=0, column=2, padx=5) ttk.Button(button_frame, text="Close", command=self._close_window).grid( row=0, column=3, padx=(5, 0) ) def _populate_medicine_list(self): """Populate the medicine list.""" # Clear existing items for item in self.tree.get_children(): self.tree.delete(item) # Add medicines for medicine in self.medicine_manager.get_all_medicines().values(): self.tree.insert( "", "end", values=( medicine.key, medicine.display_name, medicine.dosage_info, ", ".join(medicine.quick_doses), medicine.color, "Yes" if medicine.default_enabled else "No", ), ) def _add_medicine(self): """Add a new medicine.""" MedicineEditDialog( self.window, self.medicine_manager, None, self._on_medicine_changed ) def _edit_medicine(self): """Edit selected medicine.""" selection = self.tree.selection() if not selection: messagebox.showwarning("No Selection", "Please select a medicine to edit.") return item = self.tree.item(selection[0]) medicine_key = item["values"][0] medicine = self.medicine_manager.get_medicine(medicine_key) if medicine: MedicineEditDialog( self.window, self.medicine_manager, medicine, self._on_medicine_changed ) def _remove_medicine(self): """Remove selected medicine.""" selection = self.tree.selection() if not selection: messagebox.showwarning( "No Selection", "Please select a medicine to remove." ) return item = self.tree.item(selection[0]) medicine_key = item["values"][0] medicine_name = item["values"][1] if messagebox.askyesno( "Confirm Removal", f"Are you sure you want to remove '{medicine_name}'?\n\n" "This will also remove all associated data from your records!", ): if self.medicine_manager.remove_medicine(medicine_key): messagebox.showinfo( "Success", f"'{medicine_name}' removed successfully!" ) self._populate_medicine_list() self._refresh_main_app() else: messagebox.showerror("Error", f"Failed to remove '{medicine_name}'.") def _on_medicine_changed(self): """Called when a medicine is added or edited.""" self._populate_medicine_list() self._refresh_main_app() def _refresh_main_app(self): """Refresh the main application after medicine changes.""" if self.refresh_callback: self.refresh_callback() def _close_window(self): """Close the window.""" self.window.destroy() class MedicineEditDialog: """Dialog for adding/editing a medicine.""" def __init__( self, parent: tk.Toplevel, medicine_manager: MedicineManager, medicine: Medicine | None, callback, ): self.parent = parent self.medicine_manager = medicine_manager self.medicine = medicine self.callback = callback self.is_edit = medicine is not None # Create dialog self.dialog = tk.Toplevel(parent) self.dialog.title("Edit Medicine" if self.is_edit else "Add Medicine") self.dialog.geometry("400x350") self.dialog.resizable(False, False) # Make modal self.dialog.transient(parent) self.dialog.grab_set() self._setup_dialog() self._populate_fields() # Center dialog self.dialog.update_idletasks() x = parent.winfo_x() + (parent.winfo_width() // 2) - (400 // 2) y = parent.winfo_y() + (parent.winfo_height() // 2) - (350 // 2) self.dialog.geometry(f"400x350+{x}+{y}") def _setup_dialog(self): """Set up the dialog UI.""" main_frame = ttk.Frame(self.dialog, padding="15") main_frame.grid(row=0, column=0, sticky="nsew") self.dialog.grid_rowconfigure(0, weight=1) self.dialog.grid_columnconfigure(0, weight=1) # Fields fields_frame = ttk.Frame(main_frame) fields_frame.grid(row=0, column=0, sticky="ew", pady=(0, 15)) fields_frame.grid_columnconfigure(1, weight=1) row = 0 # Key ttk.Label(fields_frame, text="Key:").grid(row=row, column=0, sticky="w", pady=5) self.key_var = tk.StringVar() key_entry = ttk.Entry(fields_frame, textvariable=self.key_var) key_entry.grid(row=row, column=1, sticky="ew", padx=(10, 0), pady=5) if self.is_edit: key_entry.configure(state="readonly") row += 1 # Display Name ttk.Label(fields_frame, text="Display Name:").grid( row=row, column=0, sticky="w", pady=5 ) self.name_var = tk.StringVar() ttk.Entry(fields_frame, textvariable=self.name_var).grid( row=row, column=1, sticky="ew", padx=(10, 0), pady=5 ) row += 1 # Dosage Info ttk.Label(fields_frame, text="Dosage Info:").grid( row=row, column=0, sticky="w", pady=5 ) self.dosage_var = tk.StringVar() ttk.Entry(fields_frame, textvariable=self.dosage_var).grid( row=row, column=1, sticky="ew", padx=(10, 0), pady=5 ) row += 1 # Quick Doses ttk.Label(fields_frame, text="Quick Doses:").grid( row=row, column=0, sticky="w", pady=5 ) self.doses_var = tk.StringVar() ttk.Entry(fields_frame, textvariable=self.doses_var).grid( row=row, column=1, sticky="ew", padx=(10, 0), pady=5 ) ttk.Label( fields_frame, text="(comma-separated, e.g. 25,50,100)", font=("Arial", 8) ).grid(row=row + 1, column=1, sticky="w", padx=(10, 0)) row += 2 # Color ttk.Label(fields_frame, text="Graph Color:").grid( row=row, column=0, sticky="w", pady=5 ) self.color_var = tk.StringVar() ttk.Entry(fields_frame, textvariable=self.color_var).grid( row=row, column=1, sticky="ew", padx=(10, 0), pady=5 ) ttk.Label( fields_frame, text="(hex color, e.g. #FF6B6B)", font=("Arial", 8) ).grid(row=row + 1, column=1, sticky="w", padx=(10, 0)) row += 2 # Default Enabled self.default_var = tk.BooleanVar() ttk.Checkbutton( fields_frame, text="Show in graph by default", variable=self.default_var, ).grid(row=row, column=0, columnspan=2, sticky="w", pady=5) # Buttons button_frame = ttk.Frame(main_frame) button_frame.grid(row=1, column=0) ttk.Button(button_frame, text="Save", command=self._save_medicine).grid( row=0, column=0, padx=(0, 10) ) ttk.Button(button_frame, text="Cancel", command=self.dialog.destroy).grid( row=0, column=1 ) def _populate_fields(self): """Populate fields if editing.""" if self.medicine: self.key_var.set(self.medicine.key) self.name_var.set(self.medicine.display_name) self.dosage_var.set(self.medicine.dosage_info) self.doses_var.set(",".join(self.medicine.quick_doses)) self.color_var.set(self.medicine.color) self.default_var.set(self.medicine.default_enabled) def _save_medicine(self): """Save the medicine.""" # Validate fields key = self.key_var.get().strip() name = self.name_var.get().strip() dosage = self.dosage_var.get().strip() doses_str = self.doses_var.get().strip() color = self.color_var.get().strip() if not all([key, name, dosage, doses_str, color]): messagebox.showerror("Error", "All fields are required.") return # Validate key format (alphanumeric and underscores only) if not key.replace("_", "").replace("-", "").isalnum(): messagebox.showerror( "Error", "Key must contain only letters, numbers, underscores, and hyphens.", ) return # Parse quick doses try: quick_doses = [dose.strip() for dose in doses_str.split(",")] quick_doses = [dose for dose in quick_doses if dose] # Remove empty strings if not quick_doses: raise ValueError("At least one quick dose is required.") except Exception: messagebox.showerror("Error", "Quick doses must be comma-separated values.") return # Validate color format if not color.startswith("#") or len(color) != 7: messagebox.showerror( "Error", "Color must be in hex format (e.g., #FF6B6B)." ) return try: int(color[1:], 16) # Validate hex color except ValueError: messagebox.showerror("Error", "Invalid hex color format.") return # Create medicine object new_medicine = Medicine( key=key, display_name=name, dosage_info=dosage, quick_doses=quick_doses, color=color, default_enabled=self.default_var.get(), ) # Save medicine success = False if self.is_edit: success = self.medicine_manager.update_medicine( self.medicine.key, new_medicine ) else: success = self.medicine_manager.add_medicine(new_medicine) if success: action = "updated" if self.is_edit else "added" messagebox.showinfo("Success", f"Medicine {action} successfully!") self.callback() self.dialog.destroy() else: action = "update" if self.is_edit else "add" messagebox.showerror("Error", f"Failed to {action} medicine.")