d7d4b332d4
- Implemented MedicineManagementWindow for adding, editing, and removing medicines. - Created MedicineManager to handle medicine configurations, including loading and saving to JSON. - Updated UIManager to dynamically generate medicine-related UI components based on the MedicineManager. - Enhanced test suite with mock objects for MedicineManager to ensure proper functionality in DataManager tests. - Added validation for medicine input fields in the UI. - Introduced default medicine configurations for initial setup.
402 lines
14 KiB
Python
402 lines
14 KiB
Python
"""
|
|
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.")
|