285 lines
10 KiB
Python
285 lines
10 KiB
Python
import os
|
|
import sys
|
|
import tkinter as tk
|
|
from collections.abc import Callable
|
|
from tkinter import messagebox
|
|
from typing import Any
|
|
|
|
import pandas as pd
|
|
|
|
from constants import LOG_LEVEL, LOG_PATH
|
|
from data_manager import DataManager
|
|
from graph_manager import GraphManager
|
|
from init import logger
|
|
from ui_manager import UIManager
|
|
|
|
|
|
class MedTrackerApp:
|
|
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)
|
|
|
|
# Set up data file
|
|
self.filename: str = "thechart_data.csv"
|
|
first_argument: str = ""
|
|
|
|
if len(sys.argv) > 1:
|
|
first_argument: str = sys.argv[1]
|
|
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} doesn't exist. \
|
|
Using default file: {self.filename}"
|
|
)
|
|
|
|
if LOG_LEVEL == "DEBUG":
|
|
logger.debug(f"Script name: {sys.argv[0]}")
|
|
logger.debug(f"Logs path: {LOG_PATH}")
|
|
logger.debug(f"First argument: {first_argument}")
|
|
|
|
# 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[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},
|
|
],
|
|
)
|
|
|
|
# --- Create Table Frame ---
|
|
table_ui: dict[str, Any] = self.ui_manager.create_table_frame(main_frame)
|
|
self.tree: ttk.Treeview = table_ui["tree"]
|
|
self.tree.bind("<Double-1>", self.on_double_click)
|
|
|
|
# Load data
|
|
self.load_data()
|
|
|
|
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")
|
|
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[str, ...]) -> None:
|
|
"""Create a new Toplevel window for editing an entry."""
|
|
original_date = values[0] # Store the original date
|
|
|
|
# Define callbacks for edit window buttons
|
|
callbacks: dict[str, Callable] = {
|
|
"save": lambda win, *args: self._save_edit(win, original_date, *args),
|
|
"delete": lambda win: self._delete_entry(win, item_id),
|
|
}
|
|
|
|
# Create edit window using UI manager
|
|
_: tk.Toplevel = self.ui_manager.create_edit_window(values, callbacks)
|
|
|
|
def _save_edit(
|
|
self,
|
|
edit_win: tk.Toplevel,
|
|
original_date: str,
|
|
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."""
|
|
values: list[str | int] = [
|
|
date,
|
|
dep,
|
|
anx,
|
|
slp,
|
|
app,
|
|
bup,
|
|
hydro,
|
|
gaba,
|
|
prop,
|
|
note,
|
|
]
|
|
|
|
if self.data_manager.update_entry(original_date, values):
|
|
edit_win.destroy()
|
|
messagebox.showinfo(
|
|
"Success", "Entry updated successfully!", parent=self.root
|
|
)
|
|
self._clear_entries()
|
|
self.load_data()
|
|
else:
|
|
# Check if it's a duplicate date issue
|
|
df = self.data_manager.load_data()
|
|
if original_date != date and not df.empty and date in df["date"].values:
|
|
messagebox.showerror(
|
|
"Error",
|
|
f"An entry for date '{date}' already exists. "
|
|
"Please use a different date.",
|
|
parent=edit_win,
|
|
)
|
|
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
|
|
):
|
|
self.graph_manager.close()
|
|
self.root.destroy()
|
|
|
|
def add_entry(self) -> None:
|
|
"""Add a new entry to the CSV file."""
|
|
entry: list[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}")
|
|
|
|
# Check if date is empty
|
|
if not self.date_var.get().strip():
|
|
messagebox.showerror("Error", "Please enter a date.", parent=self.root)
|
|
return
|
|
|
|
if self.data_manager.add_entry(entry):
|
|
messagebox.showinfo(
|
|
"Success", "Entry added successfully!", parent=self.root
|
|
)
|
|
self._clear_entries()
|
|
self.load_data()
|
|
else:
|
|
# Check if it's a duplicate date by trying to load existing data
|
|
df = self.data_manager.load_data()
|
|
if not df.empty and self.date_var.get() in df["date"].values:
|
|
messagebox.showerror(
|
|
"Error",
|
|
f"An entry for date '{self.date_var.get()}' already exists. "
|
|
"Please use a different date or edit the existing entry.",
|
|
parent=self.root,
|
|
)
|
|
else:
|
|
messagebox.showerror("Error", "Failed to add entry", parent=self.root)
|
|
|
|
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,
|
|
):
|
|
# Get the date of the entry to delete
|
|
date: str = self.tree.item(item_id, "values")[0]
|
|
logger.debug(f"Deleting entry with date={date}")
|
|
|
|
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:
|
|
"""Clear all input fields."""
|
|
logger.debug("Clearing input fields.")
|
|
self.date_var.set("")
|
|
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)
|
|
|
|
# Load data from the CSV file
|
|
df: pd.DataFrame = self.data_manager.load_data()
|
|
|
|
# Update the treeview with the data
|
|
if not df.empty:
|
|
for _index, row in df.iterrows():
|
|
self.tree.insert(parent="", index="end", values=list(row))
|
|
logger.debug(f"Loaded {len(df)} entries into treeview.")
|
|
|
|
# Update the graph
|
|
self.graph_manager.update_graph(df)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
root: tk.Tk = tk.Tk()
|
|
app: MedTrackerApp = MedTrackerApp(root)
|
|
root.mainloop()
|