import csv 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 class MedTrackerApp: def __init__(self, root): self.root = 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") # self.root.geometry("800x600") 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 ) 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 ) 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.bind("", 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) 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: """Handle double-click event to edit an entry.""" 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) def create_edit_window(self, item_id: str, values: tuple) -> None: """Create a new Toplevel window for editing an entry.""" edit_win = tk.Toplevel(self.root) edit_win.title("Edit Entry") # Unpack values date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values # 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_=1, 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_=1, 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_=1, 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_=1, 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( self, edit_win: tk.Toplevel, date: str, dep: int, anx: int, slp: int, app: int, bup: int, hydro: int, gaba: int, 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) edit_win.destroy() messagebox.showinfo("Success", "Entry updated successfully!") self.clear_entries() self.load_data() def on_closing(self) -> None: if messagebox.askokcancel("Quit", "Do you want to quit the application?"): plt.close(self.fig) 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(), ] ) messagebox.showinfo("Success", "Entry added successfully!") 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. """ if messagebox.askyesno( "Delete Entry", "Are you sure you want to delete this entry?" ): 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) edit_win.destroy() messagebox.showinfo("Success", "Entry deleted successfully!") self.load_data() def clear_entries(self) -> None: 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) self.note_var.set("") def load_data(self) -> None: 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()) def update_graph(self, df: pd.DataFrame) -> None: self.ax.clear() if not df.empty: 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", ) self.ax.plot( df.index, df["anxiety"], marker="o", linestyle="-", label="Anxiety" ) self.ax.plot( df.index, df["sleep"], marker="o", linestyle="-", label="Sleep" ) self.ax.plot( df.index, df["appetite"], marker="o", linestyle="-", label="Appetite" ) 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() if __name__ == "__main__": root = tk.Tk() app = MedTrackerApp(root) root.mainloop()