Refactored...

This commit is contained in:
William Valentin
2025-07-17 12:20:20 -07:00
parent c232cc631d
commit 29ccc5fbd9

View File

@@ -1,7 +1,6 @@
import csv import csv
import os import os
import tkinter as tk import tkinter as tk
from datetime import datetime
from tkinter import messagebox, ttk from tkinter import messagebox, ttk
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
@@ -12,18 +11,21 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
class MedTrackerApp: class MedTrackerApp:
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
self.root.resizable(True, True)
# self.root.iconbitmap("./snog_skype.jpg")
self.root.title("Thechart - medication tracker") self.root.title("Thechart - medication tracker")
self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# self.root.geometry("800x600")
self.filename = "thechart_data.csv" self.filename = "thechart_data.csv"
self.initialize_csv() self.initialize_csv()
main_frame = ttk.Frame(self.root, padding="10") main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) main_frame.grid(row=0, column=0)
# --- Input Frame --- # --- Input Frame ---
input_frame = ttk.LabelFrame(main_frame, text="New Entry") input_frame = ttk.LabelFrame(main_frame, text="New Entry")
input_frame.grid(row=0, column=0, padx=10, pady=10, sticky="ew") input_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
ttk.Label(input_frame, text="Depression (0-10):").grid( ttk.Label(input_frame, text="Depression (0-10):").grid(
row=0, column=0, sticky="w", padx=5, pady=2 row=0, column=0, sticky="w", padx=5, pady=2
@@ -65,16 +67,69 @@ class MedTrackerApp:
variable=self.appetite_var, variable=self.appetite_var,
).grid(row=3, column=1, sticky="ew") ).grid(row=3, column=1, sticky="ew")
ttk.Label(input_frame, text="Note:").grid( ttk.Label(input_frame, text="Treatment:").grid(
row=4, column=0, sticky="w", padx=5, pady=2 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() self.note_var = tk.StringVar()
ttk.Entry(input_frame, textvariable=self.note_var).grid( ttk.Entry(input_frame, textvariable=self.note_var).grid(
row=4, column=1, sticky="ew", padx=5, pady=2 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 = ttk.Frame(input_frame)
button_frame.grid(row=5, column=0, columnspan=2, pady=10) button_frame.grid(row=7, column=0, columnspan=2, pady=10)
ttk.Button(button_frame, text="Add Entry", command=self.add_entry).pack( ttk.Button(button_frame, text="Add Entry", command=self.add_entry).pack(
side="left", padx=5 side="left", padx=5
) )
@@ -84,20 +139,46 @@ class MedTrackerApp:
# --- Table Frame --- # --- Table Frame ---
table_frame = ttk.LabelFrame(main_frame, text="Log (Double-click to edit)") table_frame = ttk.LabelFrame(main_frame, text="Log (Double-click to edit)")
table_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew") table_frame.grid(row=1, column=1, padx=10, pady=10, sticky="nsew")
self.tree = ttk.Treeview( self.tree = ttk.Treeview(
table_frame, table_frame,
columns=("Timestamp", "Depression", "Anxiety", "Sleep", "Appetite", "Note"), columns=(
"Date",
"Depression",
"Anxiety",
"Sleep",
"Appetite",
"Bupropion",
"Hydroxyzine",
"Gabapentin",
"Propranolol",
"Note",
),
show="headings", show="headings",
) )
self.tree.heading("Timestamp", text="Timestamp") self.tree.heading("Date", text="Date")
self.tree.heading("Depression", text="Depression") self.tree.heading("Depression", text="Depression")
self.tree.heading("Anxiety", text="Anxiety") self.tree.heading("Anxiety", text="Anxiety")
self.tree.heading("Sleep", text="Sleep") self.tree.heading("Sleep", text="Sleep")
self.tree.heading("Appetite", text="Appetite") 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.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")
# --- NEW: Bind double-click event --- # --- NEW: Bind double-click event ---
self.tree.bind("<Double-1>", self.on_double_click) self.tree.bind("<Double-1>", self.on_double_click)
self.tree.pack(side="left", fill="both", expand=True) self.tree.pack(side="left", fill="both", expand=True)
@@ -110,44 +191,48 @@ class MedTrackerApp:
# --- Graph Frame --- # --- Graph Frame ---
graph_frame = ttk.LabelFrame(main_frame, text="Evolution") graph_frame = ttk.LabelFrame(main_frame, text="Evolution")
graph_frame.grid(row=0, column=1, rowspan=2, padx=10, pady=10, sticky="nsew") graph_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=10, sticky="nsew")
self.fig, self.ax = plt.subplots() self.fig, self.ax = plt.subplots()
self.canvas = FigureCanvasTkAgg(self.fig, master=graph_frame) self.canvas = FigureCanvasTkAgg(figure=self.fig, master=graph_frame)
self.canvas.get_tk_widget().pack(fill="both", expand=True) self.canvas.get_tk_widget().pack(fill="both", expand=True)
main_frame.columnconfigure(1, weight=3)
main_frame.rowconfigure(1, weight=1)
self.load_data() self.load_data()
def on_double_click(self, event) -> None: 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.""" """Handle double-click event to edit an entry."""
if len(self.tree.get_children()) > 0: if len(self.tree.get_children()) > 0:
item_id = self.tree.selection()[0] item_id = self.tree.selection()[0]
item_values = self.tree.item(item_id, "values") item_values = self.tree.item(item_id, "values")
self.create_edit_window(item_id, item_values) self.create_edit_window(item_id, item_values)
def create_edit_window(self, item_id, values) -> None: def create_edit_window(self, item_id: str, values: tuple) -> None:
"""Create a new Toplevel window for editing an entry.""" """Create a new Toplevel window for editing an entry."""
edit_win = tk.Toplevel(self.root) edit_win = tk.Toplevel(self.root)
edit_win.title("Edit Entry") edit_win.title("Edit Entry")
# Unpack values # Unpack values
ts, dep, anx, slp, app, note = values date, dep, anx, slp, app, bup, hydro, gaba, prop, note = values
# Create variables for the widgets # Create variables for the widgets
date_var = tk.StringVar(value=str(date))
dep_var = tk.IntVar(value=int(dep)) dep_var = tk.IntVar(value=int(dep))
anx_var = tk.IntVar(value=int(anx)) anx_var = tk.IntVar(value=int(anx))
slp_var = tk.IntVar(value=int(slp)) slp_var = tk.IntVar(value=int(slp))
app_var = tk.IntVar(value=int(app)) app_var = tk.IntVar(value=int(app))
note_var = tk.StringVar(value=note) 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 # Create form widgets
ttk.Label(edit_win, text=f"Timestamp: {ts}").grid(
row=0, column=0, columnspan=2, padx=5, pady=5
)
ttk.Label(edit_win, text="Depression:").grid( ttk.Label(edit_win, text="Depression:").grid(
row=1, column=0, sticky="w", padx=5, pady=2 row=1, column=0, sticky="w", padx=5, pady=2
) )
@@ -176,10 +261,54 @@ class MedTrackerApp:
edit_win, from_=1, to=10, variable=app_var, orient=tk.HORIZONTAL edit_win, from_=1, to=10, variable=app_var, orient=tk.HORIZONTAL
).grid(row=4, column=1, sticky="ew") ).grid(row=4, column=1, sticky="ew")
ttk.Label(edit_win, text="Note:").grid( ttk.Label(edit_win, text="Treatment:").grid(
row=5, column=0, sticky="w", padx=5, pady=2 row=5, column=0, sticky="w", padx=5, pady=2
) )
ttk.Entry(edit_win, textvariable=note_var).grid(row=5, column=1, sticky="ew")
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 and Cancel buttons
save_btn = ttk.Button( save_btn = ttk.Button(
@@ -187,36 +316,69 @@ class MedTrackerApp:
text="Save", text="Save",
command=lambda: self.save_edit( command=lambda: self.save_edit(
edit_win, edit_win,
ts, date_var.get(),
dep_var.get(), dep_var.get(),
anx_var.get(), anx_var.get(),
slp_var.get(), slp_var.get(),
app_var.get(), app_var.get(),
bup_var.get(),
hydro_var.get(),
gaba_var.get(),
prop_var.get(),
note_var.get(), note_var.get(),
), ),
) )
save_btn.grid(row=6, column=0, padx=5, pady=10) 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 = ttk.Button(edit_win, text="Cancel", command=edit_win.destroy)
cancel_btn.grid(row=6, column=1, padx=5, pady=10) 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, timestamp, dep: int, anx: int, slp: int, app: int, note: str 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: ) -> None:
""" """
Save the edited data to the CSV file. Save the edited data to the CSV file.
""" """
df = pd.read_csv(self.filename) df = pd.read_csv(self.filename)
# Find the row to update using the timestamp as a unique identifier # Find the row to update using the date as a unique identifier
df.loc[ df.loc[
df["timestamp"] == timestamp, df["date"] == date,
["depression", "anxiety", "sleep", "appetite", "note"], [
] = [dep, anx, slp, app, note] "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 # Write the updated dataframe back to the CSV
df.to_csv(self.filename, index=False) df.to_csv(self.filename, index=False)
edit_win.destroy() edit_win.destroy()
messagebox.showinfo("Success", "Entry updated successfully!") messagebox.showinfo("Success", "Entry updated successfully!")
self.clear_entries()
self.load_data() self.load_data()
def on_closing(self) -> None: def on_closing(self) -> None:
@@ -229,21 +391,35 @@ class MedTrackerApp:
with open(self.filename, mode="w", newline="") as file: with open(self.filename, mode="w", newline="") as file:
writer = csv.writer(file) writer = csv.writer(file)
writer.writerow( writer.writerow(
["timestamp", "depression", "anxiety", "sleep", "appetite", "note"] [
"date",
"depression",
"anxiety",
"sleep",
"appetite",
"bupropion",
"hydroxyzine",
"gabapentin",
"propranolol",
"note",
]
) )
def add_entry(self) -> None: def add_entry(self) -> None:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.filename, mode="a", newline="") as file: with open(self.filename, mode="a", newline="") as file:
writer = csv.writer(file) writer = csv.writer(file)
writer.writerow( writer.writerow(
[ [
timestamp,
self.depression_var.get(), self.depression_var.get(),
self.anxiety_var.get(), self.anxiety_var.get(),
self.sleep_var.get(), self.sleep_var.get(),
self.appetite_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(), self.note_var.get(),
self.date_var.get(),
] ]
) )
@@ -251,11 +427,35 @@ class MedTrackerApp:
self.clear_entries() self.clear_entries()
self.load_data() 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: def clear_entries(self) -> None:
self.date_var.set("")
self.depression_var.set(0) self.depression_var.set(0)
self.anxiety_var.set(0) self.anxiety_var.set(0)
self.sleep_var.set(0) self.sleep_var.set(0)
self.appetite_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("") self.note_var.set("")
def load_data(self) -> None: def load_data(self) -> None:
@@ -264,11 +464,24 @@ class MedTrackerApp:
if os.path.exists(self.filename) and os.path.getsize(self.filename) > 0: if os.path.exists(self.filename) and os.path.getsize(self.filename) > 0:
try: try:
df = pd.read_csv(self.filename, dtype={"note": str}).fillna("") df = pd.read_csv(
# Sort by timestamp to keep order consistent self.filename,
df = df.sort_values(by="timestamp").reset_index(drop=True) 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(): for index, row in df.iterrows():
self.tree.insert("", "end", values=list(row)) self.tree.insert(parent="", index="end", values=list(row))
self.update_graph(df) self.update_graph(df)
except pd.errors.EmptyDataError: except pd.errors.EmptyDataError:
self.update_graph(pd.DataFrame()) self.update_graph(pd.DataFrame())
@@ -276,8 +489,8 @@ class MedTrackerApp:
def update_graph(self, df: pd.DataFrame) -> None: def update_graph(self, df: pd.DataFrame) -> None:
self.ax.clear() self.ax.clear()
if not df.empty: if not df.empty:
df["timestamp"] = pd.to_datetime(df["timestamp"]) df = df.sort_values(by="date")
df.set_index("timestamp", inplace=True) df.set_index(keys="date", inplace=True)
self.ax.plot( self.ax.plot(
df.index, df.index,
df["depression"], df["depression"],
@@ -297,7 +510,7 @@ class MedTrackerApp:
self.ax.legend() self.ax.legend()
self.ax.set_title("Medication Effects Over Time") self.ax.set_title("Medication Effects Over Time")
self.ax.set_xlabel("Date") self.ax.set_xlabel("Date")
self.ax.set_ylabel("Rating (1-10)") self.ax.set_ylabel("Rating (0-10)")
self.fig.autofmt_xdate() self.fig.autofmt_xdate()
self.canvas.draw() self.canvas.draw()