Refactor MedTrackerApp and UI components for improved structure and readability

- Simplified initialization logic in init.py
- Consolidated testing_mode assignment
- Removed unnecessary else statements
- Created UIManager class to handle UI-related tasks
- Modularized input frame creation, table frame creation, and graph frame creation
- Enhanced edit window creation with better organization and error handling
- Updated data management methods to improve clarity and maintainability
- Improved logging for better debugging and tracking of application flow
This commit is contained in:
William Valentin
2025-07-23 16:10:22 -07:00
parent 4ba4b1b7c5
commit 2142db7093
15 changed files with 1063 additions and 578 deletions
+179 -530
View File
@@ -1,399 +1,134 @@
import csv
import logging
import os
import tkinter as tk
from tkinter import messagebox, ttk
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import sys
from PIL import Image, ImageTk
import tkinter as tk
from tkinter import messagebox
from typing import Dict, List, Tuple, Any, Callable, Union
import pandas as pd
from init import logger
from constants import LOG_LEVEL
from data_manager import DataManager
from graph_manager import GraphManager
from ui_manager import UIManager
class MedTrackerApp:
def __init__(self, root):
self.root = root
def __init__(self, root: tk.Tk) -> None:
self.root: tk.Tk = root
self.root.resizable(True, True)
self.root.title("Thechart - medication tracker")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# self.root.iconbitmap("app_icon.ico")
# screen_width = self.root.winfo_screenwidth()
# screen_height = self.root.winfo_screenheight()
# self.root.geometry(f"{screen_width}x{screen_height}")
# self.root.configure(background='gold')
# self.root.lift()
# self.root.attributes("-topmost", True)
# self.root.geometry("800x600")
# Set up data file
self.filename: str = "thechart_data.csv"
if len(sys.argv) > 1:
script_name = sys.argv[0]
first_argument = sys.argv[1]
first_argument: str = sys.argv[1]
if LOG_LEVEL == "DEBUG":
logger.debug(f"Script name: {script_name}")
logger.debug(f"Script name: {sys.argv[0]}")
logger.debug(f"First argument: {first_argument}")
if os.path.exists(first_argument):
self.filename = first_argument
logger.info(f"Using data file: {first_argument}")
else:
logger.warning(
f"Data file {first_argument} does not exist."
f" Using default file: {self.filename}"
f"Data file {first_argument} does not exist. Using default file: {self.filename}"
)
self.make_icon(
img="/home/will/Code/thechart/chart-671.png",
logger=logger,
# Initialize managers
self.ui_manager: UIManager = UIManager(root, logger)
self.data_manager: DataManager = DataManager(self.filename, logger)
# Set up application icon
icon_path: str = "chart-671.png"
if not os.path.exists(icon_path) and os.path.exists("./chart-671.png"):
icon_path = "./chart-671.png"
self.ui_manager.setup_icon(img_path=icon_path)
# Set up the main application UI
self._setup_main_ui()
def _setup_main_ui(self) -> None:
"""Set up the main UI components."""
import tkinter.ttk as ttk
# --- Main Frame ---
main_frame: ttk.Frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky="nsew")
# Configure root window grid
self.root.grid_rowconfigure(0, weight=1)
self.root.grid_columnconfigure(0, weight=1)
# Configure main frame grid for scaling
for i in range(2):
main_frame.grid_rowconfigure(i, weight=1 if i == 1 else 0)
main_frame.grid_columnconfigure(i, weight=3 if i == 1 else 1)
logger.debug("Main frame and root grid configured for scaling.")
# --- Create Graph Frame ---
graph_frame: ttk.Frame = self.ui_manager.create_graph_frame(main_frame)
self.graph_manager: GraphManager = GraphManager(graph_frame)
# --- Create Input Frame ---
input_ui: Dict[str, Any] = self.ui_manager.create_input_frame(
main_frame
)
self.input_frame: ttk.Frame = input_ui["frame"]
self.symptom_vars: Dict[str, tk.IntVar] = input_ui["symptom_vars"]
self.medicine_vars: Dict[str, List[Union[tk.IntVar, ttk.Spinbox]]] = (
input_ui["medicine_vars"]
)
self.note_var: tk.StringVar = input_ui["note_var"]
self.date_var: tk.StringVar = input_ui["date_var"]
# Add buttons to input frame
self.ui_manager.add_buttons(
self.input_frame,
[
{
"text": "Add Entry",
"command": self.add_entry,
"fill": "both",
"expand": True,
},
{"text": "Quit", "command": self.on_closing},
],
)
self.filename = "thechart_data.csv"
self.initialize_csv()
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0)
# --- Input Frame ---
input_frame = ttk.LabelFrame(main_frame, text="New Entry")
input_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
ttk.Label(input_frame, text="Depression (0-10):").grid(
row=0, column=0, sticky="w", padx=5, pady=2
# --- Create Table Frame ---
table_ui: Dict[str, Any] = self.ui_manager.create_table_frame(
main_frame
)
self.depression_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.depression_var,
).grid(row=0, column=1, sticky="ew")
ttk.Label(input_frame, text="Anxiety (0-10):").grid(
row=1, column=0, sticky="w", padx=5, pady=2
)
self.anxiety_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.anxiety_var,
).grid(row=1, column=1, sticky="ew")
ttk.Label(input_frame, text="Sleep Quality (0-10):").grid(
row=2, column=0, sticky="w", padx=5, pady=2
)
self.sleep_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.sleep_var,
).grid(row=2, column=1, sticky="ew")
ttk.Label(input_frame, text="Appetite (0-10):").grid(
row=3, column=0, sticky="w", padx=5, pady=2
)
self.appetite_var = tk.IntVar()
ttk.Scale(
input_frame,
from_=0,
to=10,
orient=tk.HORIZONTAL,
variable=self.appetite_var,
).grid(row=3, column=1, sticky="ew")
ttk.Label(input_frame, text="Treatment:").grid(
row=4, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(input_frame, text="Medicine")
medicine_frame.grid(row=4, column=1, padx=0, pady=10, sticky="nsew")
self.bupropion_var = tk.IntVar(value=0)
self.hydroxyzine_var = tk.IntVar(value=0)
self.gabapentin_var = tk.IntVar(value=0)
self.propranolol_var = tk.IntVar(value=0)
ttk.Checkbutton(
medicine_frame,
text="Bupropion 150mg",
variable=self.bupropion_var,
name="bupropion_check",
command=lambda: self.toggle_checkbox(obj_name="bupropion_check"),
).grid(row=0, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Hydroxyzine 25mg",
variable=self.hydroxyzine_var,
name="hydroxyzine_check",
command=lambda: self.toggle_checkbox(obj_name="hydroxyzine_check"),
).grid(row=1, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Gabapentin 100mg",
variable=self.gabapentin_var,
name="gabapentin_check",
command=lambda: self.toggle_checkbox(obj_name="gabapentin_check"),
).grid(row=2, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Propranolol 10mg",
name="propranolol_check",
variable=self.propranolol_var,
command=lambda: self.toggle_checkbox(obj_name="propranolol_check"),
).grid(row=3, column=0, sticky="w", padx=5, pady=2)
ttk.Label(input_frame, text="Note:").grid(
row=5, column=0, sticky="w", padx=5, pady=2
)
self.note_var = tk.StringVar()
ttk.Entry(input_frame, textvariable=self.note_var).grid(
row=5, column=1, sticky="ew", padx=5, pady=2
)
ttk.Label(input_frame, text="Date (mm/dd/yyyy):").grid(
row=6, column=0, sticky="w", padx=5, pady=2
)
self.date_var = tk.StringVar()
ttk.Entry(
input_frame, textvariable=self.date_var, justify="center"
).grid(row=6, column=1, sticky="ew", padx=5, pady=2)
button_frame = ttk.Frame(input_frame)
button_frame.grid(row=7, column=0, columnspan=2, pady=10)
ttk.Button(
button_frame, text="Add Entry", command=self.add_entry
).pack(side="left", padx=5, fill="both", expand=True)
ttk.Button(button_frame, text="Quit", command=self.on_closing).pack(
side="left", padx=5
)
# --- Table Frame ---
table_frame = ttk.LabelFrame(
main_frame, text="Log (Double-click to edit)"
)
table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
self.tree = ttk.Treeview(
table_frame,
columns=(
"Date",
"Depression",
"Anxiety",
"Sleep",
"Appetite",
"Bupropion",
"Hydroxyzine",
"Gabapentin",
"Propranolol",
"Note",
),
show="headings",
)
self.tree.heading("Date", text="Date")
self.tree.heading("Depression", text="Depression")
self.tree.heading("Anxiety", text="Anxiety")
self.tree.heading("Sleep", text="Sleep")
self.tree.heading("Appetite", text="Appetite")
self.tree.heading("Bupropion", text="Bupropion 150mg")
self.tree.heading("Hydroxyzine", text="Hydroxyzine 25mg")
self.tree.heading("Gabapentin", text="Gabapentin 100mg")
self.tree.heading("Propranolol", text="Propranolol 10mg")
self.tree.heading("Note", text="Note")
self.tree.column("Date", width=80, anchor="center")
self.tree.column("Depression", width=80, anchor="center")
self.tree.column("Anxiety", width=80, anchor="center")
self.tree.column("Sleep", width=80, anchor="center")
self.tree.column("Appetite", width=80, anchor="center")
self.tree.column("Bupropion", width=120, anchor="center")
self.tree.column("Hydroxyzine", width=120, anchor="center")
self.tree.column("Gabapentin", width=120, anchor="center")
self.tree.column("Propranolol", width=120, anchor="center")
self.tree.column("Note", width=300, anchor="w")
# --- Bind double-click event ---
self.tree: ttk.Treeview = table_ui["tree"]
self.tree.bind("<Double-1>", self.on_double_click)
self.tree.pack(side="left", fill="both", expand=True)
scrollbar = ttk.Scrollbar(
table_frame, orient="vertical", command=self.tree.yview
)
self.tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
# --- Graph Frame ---
graph_frame = ttk.LabelFrame(main_frame, text="Evolution")
graph_frame.grid(
row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew"
)
self.fig, self.ax = plt.subplots()
self.canvas = FigureCanvasTkAgg(figure=self.fig, master=graph_frame)
self.canvas.get_tk_widget().pack(fill="both", expand=True)
# Load data
self.load_data()
def toggle_checkbox(obj_name: str) -> None:
if ttk.Checkbutton.nametowidget(name=obj_name).get():
ttk.Checkbutton.nametowidget(name=obj_name).set(False)
else:
ttk.Checkbutton.nametowidget(name=obj_name).set(True)
def on_double_click(self, event: any) -> None:
def on_double_click(self, event: tk.Event) -> None:
"""Handle double-click event to edit an entry."""
logger.debug("Double-click event triggered on treeview.")
if len(self.tree.get_children()) > 0:
item_id = self.tree.selection()[0]
item_values = self.tree.item(item_id, "values")
self.create_edit_window(item_id, item_values)
logger.debug(f"Editing item_id={item_id}, values={item_values}")
self._create_edit_window(item_id, item_values)
def create_edit_window(self, item_id: str, values: tuple) -> None:
def _create_edit_window(
self, item_id: str, values: Tuple[str, ...]
) -> None:
"""Create a new Toplevel window for editing an entry."""
edit_win = tk.Toplevel(master=self.root)
edit_win.title("Edit Entry")
# Define callbacks for edit window buttons
callbacks: Dict[str, Callable] = {
"save": self._save_edit,
"delete": lambda win: self._delete_entry(win, item_id),
}
# Unpack values
date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
# Create edit window using UI manager
_: tk.Toplevel = self.ui_manager.create_edit_window(values, callbacks)
# Create variables for the widgets
date_var = tk.StringVar(value=str(date))
dep_var = tk.IntVar(value=int(dep))
anx_var = tk.IntVar(value=int(anx))
slp_var = tk.IntVar(value=int(slp))
app_var = tk.IntVar(value=int(app))
bup_var = tk.IntVar(value=int(bup))
hydro_var = tk.IntVar(value=int(hydro))
gaba_var = tk.IntVar(value=int(gaba))
prop_var = tk.IntVar(value=int(prop))
note_var = tk.StringVar(value=str(note))
# Create form widgets
ttk.Label(edit_win, text="Depression:").grid(
row=1, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=0, to=10, variable=dep_var, orient=tk.HORIZONTAL
).grid(row=1, column=1, sticky="ew")
ttk.Label(edit_win, text="Anxiety:").grid(
row=2, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=0, to=10, variable=anx_var, orient=tk.HORIZONTAL
).grid(row=2, column=1, sticky="ew")
ttk.Label(edit_win, text="Sleep:").grid(
row=3, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=0, to=10, variable=slp_var, orient=tk.HORIZONTAL
).grid(row=3, column=1, sticky="ew")
ttk.Label(edit_win, text="Appetite:").grid(
row=4, column=0, sticky="w", padx=5, pady=2
)
ttk.Scale(
edit_win, from_=0, to=10, variable=app_var, orient=tk.HORIZONTAL
).grid(row=4, column=1, sticky="ew")
ttk.Label(edit_win, text="Treatment:").grid(
row=5, column=0, sticky="w", padx=5, pady=2
)
medicine_frame = ttk.LabelFrame(edit_win, text="Medicine")
medicine_frame.grid(row=5, column=1, padx=0, pady=10, sticky="nsew")
ttk.Checkbutton(
medicine_frame,
text="Bupropion 150mg",
name="bupropion_check",
variable=bup_var,
command=lambda: self.toggle_checkbox(obj_name="bupropion_check"),
).grid(row=0, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Hydroxyzine 25mg",
name="hydroxyzine_check",
variable=hydro_var,
command=lambda: self.toggle_checkbox(obj_name="hydroxyzine_check"),
).grid(row=1, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Gabapentin 100mg",
name="gabapentin_check",
variable=gaba_var,
command=lambda: self.toggle_checkbox(obj_name="gabapentin_check"),
).grid(row=2, column=0, sticky="w", padx=5, pady=2)
ttk.Checkbutton(
medicine_frame,
text="Propranolol 10mg",
name="propranolol_check",
variable=prop_var,
command=lambda: self.toggle_checkbox(obj_name="propranolol_check"),
).grid(row=3, column=0, sticky="w", padx=5, pady=2)
ttk.Label(edit_win, text="Note:").grid(
row=6, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(edit_win, textvariable=note_var).grid(
row=6, column=1, sticky="ew"
)
ttk.Label(edit_win, text="Date:").grid(
row=7, column=0, sticky="w", padx=5, pady=2
)
ttk.Entry(edit_win, textvariable=date_var).grid(
row=7, column=1, sticky="ew"
)
# Save and Cancel buttons
save_btn = ttk.Button(
edit_win,
text="Save",
command=lambda: self.save_edit(
edit_win,
date_var.get(),
dep_var.get(),
anx_var.get(),
slp_var.get(),
app_var.get(),
bup_var.get(),
hydro_var.get(),
gaba_var.get(),
prop_var.get(),
note_var.get(),
),
)
save_btn.grid(row=8, column=0, padx=5, pady=10)
cancel_btn = ttk.Button(
edit_win, text="Cancel", command=edit_win.destroy
)
cancel_btn.grid(row=8, column=1, padx=5, pady=10)
delete_btn = ttk.Button(
edit_win,
text="Delete",
command=lambda: self.delete_entry(edit_win, item_id),
)
delete_btn.grid(row=8, column=2, padx=5, pady=10)
def save_edit(
def _save_edit(
self,
edit_win: tk.Toplevel,
date: str,
@@ -407,207 +142,121 @@ class MedTrackerApp:
prop: int,
note: str,
) -> None:
"""
Save the edited data to the CSV file.
"""
df = pd.read_csv(self.filename)
# Find the row to update using the date as a unique identifier
df.loc[
df["date"] == date,
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
],
] = [date, dep, anx, slp, app, bup, hydro, gaba, prop, note]
# Write the updated dataframe back to the CSV
df.to_csv(self.filename, index=False)
"""Save the edited data to the CSV file."""
values: List[Union[str, int]] = [
date,
dep,
anx,
slp,
app,
bup,
hydro,
gaba,
prop,
note,
]
edit_win.destroy()
messagebox.showinfo(
"Success", "Entry updated successfully!", parent=self.root
)
self.clear_entries()
self.load_data()
if self.data_manager.update_entry(date, values):
edit_win.destroy()
messagebox.showinfo(
"Success", "Entry updated successfully!", parent=self.root
)
self._clear_entries()
self.load_data()
else:
messagebox.showerror(
"Error", "Failed to save changes", parent=edit_win
)
def on_closing(self) -> None:
if messagebox.askokcancel(
"Quit", "Do you want to quit the application?", parent=self.root
):
plt.close(self.fig)
self.graph_manager.close()
self.root.destroy()
def initialize_csv(self) -> None:
if not os.path.exists(self.filename):
with open(self.filename, mode="w", newline="") as file:
writer = csv.writer(file)
writer.writerow(
[
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
]
)
def add_entry(self) -> None:
with open(self.filename, mode="a", newline="") as file:
writer = csv.writer(file)
writer.writerow(
[
self.date_var.get(),
self.depression_var.get(),
self.anxiety_var.get(),
self.sleep_var.get(),
self.appetite_var.get(),
self.bupropion_var.get(),
self.hydroxyzine_var.get(),
self.gabapentin_var.get(),
self.propranolol_var.get(),
self.note_var.get(),
]
"""Add a new entry to the CSV file."""
entry: List[Union[str, int]] = [
self.date_var.get(),
self.symptom_vars["depression"].get(),
self.symptom_vars["anxiety"].get(),
self.symptom_vars["sleep"].get(),
self.symptom_vars["appetite"].get(),
self.medicine_vars["bupropion"][0].get(),
self.medicine_vars["hydroxyzine"][0].get(),
self.medicine_vars["gabapentin"][0].get(),
self.medicine_vars["propranolol"][0].get(),
self.note_var.get(),
]
logger.debug(f"Adding entry: {entry}")
if self.data_manager.add_entry(entry):
messagebox.showinfo(
"Success", "Entry added successfully!", parent=self.root
)
self._clear_entries()
self.load_data()
else:
messagebox.showerror(
"Error", "Failed to add entry", parent=self.root
)
messagebox.showinfo(
"Success", "Entry added successfully!", parent=self.root
)
self.clear_entries()
self.load_data()
def delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
"""
Delete the selected entry from the CSV file.
"""
def _delete_entry(self, edit_win: tk.Toplevel, item_id: str) -> None:
"""Delete the selected entry from the CSV file."""
logger.debug(f"Delete requested for item_id={item_id}")
if messagebox.askyesno(
"Delete Entry",
"Are you sure you want to delete this entry?",
parent=edit_win,
):
df = pd.read_csv(self.filename)
# Get the date of the entry to delete
date = self.tree.item(item_id, "values")[0]
# Remove the row with the matching date
df = df[df["date"] != date]
# Write the updated dataframe back to the CSV
df.to_csv(self.filename, index=False)
date: str = self.tree.item(item_id, "values")[0]
logger.debug(f"Deleting entry with date={date}")
edit_win.destroy()
messagebox.showinfo(
"Success", "Entry deleted successfully!", parent=edit_win
)
self.load_data()
if self.data_manager.delete_entry(date):
edit_win.destroy()
messagebox.showinfo(
"Success", "Entry deleted successfully!", parent=edit_win
)
self.load_data()
else:
messagebox.showerror(
"Error", "Failed to delete entry", parent=edit_win
)
def clear_entries(self) -> None:
def _clear_entries(self) -> None:
"""Clear all input fields."""
logger.debug("Clearing input fields.")
self.date_var.set("")
self.depression_var.set(0)
self.anxiety_var.set(0)
self.sleep_var.set(0)
self.appetite_var.set(0)
self.bupropion_var.set(False)
self.hydroxyzine_var.set(False)
self.gabapentin_var.set(False)
self.propranolol_var.set(False)
for key in self.symptom_vars:
self.symptom_vars[key].set(0)
for key in self.medicine_vars:
self.medicine_vars[key][0].set(0)
self.note_var.set("")
def load_data(self) -> None:
"""Load data from the CSV file into the table and graph."""
logger.debug("Loading data from CSV.")
# Clear existing data in the treeview
for i in self.tree.get_children():
self.tree.delete(i)
if (
os.path.exists(self.filename)
and os.path.getsize(self.filename) > 0
):
try:
df = pd.read_csv(
self.filename,
dtype={
"depression": int,
"anxiety": int,
"sleep": int,
"appetite": int,
"bupropion": int,
"hydroxyzine": int,
"gabapentin": int,
"propranolol": int,
"note": str,
"date": str,
},
).fillna("")
df = df.sort_values(by="date").reset_index(drop=True)
for index, row in df.iterrows():
self.tree.insert(parent="", index="end", values=list(row))
self.update_graph(df)
except pd.errors.EmptyDataError:
self.update_graph(pd.DataFrame())
# Load data from the CSV file
df: pd.DataFrame = self.data_manager.load_data()
def update_graph(self, df: pd.DataFrame) -> None:
self.ax.clear()
# Update the treeview with the data
if not df.empty:
df["date"] = pd.to_datetime(df["date"])
df = df.sort_values(by="date")
df.set_index(keys="date", inplace=True)
self.ax.plot(
df.index,
df["depression"],
marker="o",
linestyle="-",
label="Depression (0:good, 10:bad)",
)
self.ax.plot(
df.index,
df["anxiety"],
marker="o",
linestyle="-",
label="Anxiety (0:good, 10:bad)",
)
self.ax.plot(
df.index,
df["sleep"],
marker="o",
linestyle="dashed",
label="Sleep (0:bad, 10:good)",
)
self.ax.plot(
df.index,
df["appetite"],
marker="o",
linestyle="dashed",
label="Appetite (0:bad, 10:good)",
)
self.ax.legend()
self.ax.set_title("Medication Effects Over Time")
self.ax.set_xlabel("Date")
self.ax.set_ylabel("Rating (0-10)")
self.fig.autofmt_xdate()
self.canvas.draw()
for index, row in df.iterrows():
self.tree.insert(parent="", index="end", values=list(row))
logger.debug(f"Loaded {len(df)} entries into treeview.")
def make_icon(self, img: str, logger: logging.Logger) -> None:
try:
icon_image = Image.open(img)
icon_image = icon_image.resize(
size=(32, 32), resample=Image.Resampling.NEAREST
)
icon_photo = ImageTk.PhotoImage(image=icon_image)
self.root.iconphoto(True, icon_photo)
self.root.wm_iconphoto(True, icon_photo)
except FileNotFoundError:
logger.warning("Icon file not found.")
# Update the graph
self.graph_manager.update_graph(df)
if __name__ == "__main__":
root = tk.Tk()
app = MedTrackerApp(root)
root: tk.Tk = tk.Tk()
app: MedTrackerApp = MedTrackerApp(root)
root.mainloop()