Files
ExerciseDiary/ui/daily_log.py

127 lines
5.9 KiB
Python

import customtkinter as ctk
from database import Database
from tkinter import messagebox
from utils import parse_float, format_float
from gamification import GamificationManager
class DailyLogFrame(ctk.CTkFrame):
def __init__(self, master, db: Database, date_str: str):
super().__init__(master)
self.db = db
self.date_str = date_str
self.gm = GamificationManager(self.db)
self.setup_ui()
self.load_data()
def setup_ui(self):
self.grid_columnconfigure((0, 1), weight=1)
# Header
self.header = ctk.CTkLabel(self, text=f"Daily Log: {self.date_str}", font=ctk.CTkFont(size=20, weight="bold"))
self.header.grid(row=0, column=0, columnspan=2, pady=10, sticky="w")
# Sleep Section
self.sleep_frame = ctk.CTkFrame(self)
self.sleep_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=10)
ctk.CTkLabel(self.sleep_frame, text="Sleep Duration (Hours):").grid(row=0, column=0, padx=10, pady=10)
self.sleep_entry = ctk.CTkEntry(self.sleep_frame, placeholder_text="e.g. 7,5")
self.sleep_entry.grid(row=0, column=1, padx=10, pady=10)
ctk.CTkLabel(self.sleep_frame, text="Sleep Quality (1-10):").grid(row=0, column=2, padx=10, pady=10)
self.sleep_quality_slider = ctk.CTkSlider(self.sleep_frame, from_=1, to=10, number_of_steps=9)
self.sleep_quality_slider.grid(row=0, column=3, padx=10, pady=10)
self.sleep_quality_label = ctk.CTkLabel(self.sleep_frame, text="5")
self.sleep_quality_label.grid(row=0, column=4, padx=5)
self.sleep_quality_slider.configure(command=lambda val: self.sleep_quality_label.configure(text=str(int(val))))
# Body Stats Section
self.body_frame = ctk.CTkFrame(self)
self.body_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=10)
ctk.CTkLabel(self.body_frame, text="Weight (kg):").grid(row=0, column=0, padx=10, pady=10)
self.weight_entry = ctk.CTkEntry(self.body_frame, placeholder_text="0,0")
self.weight_entry.grid(row=0, column=1, padx=10, pady=10)
ctk.CTkLabel(self.body_frame, text="Waist (cm):").grid(row=0, column=2, padx=10, pady=10)
self.waist_entry = ctk.CTkEntry(self.body_frame, placeholder_text="0,0")
self.waist_entry.grid(row=0, column=3, padx=10, pady=10)
# Notes / Mood
self.notes_frame = ctk.CTkFrame(self)
self.notes_frame.grid(row=3, column=0, columnspan=2, sticky="ew", pady=10)
ctk.CTkLabel(self.notes_frame, text="Mood/Energy (1-10):").grid(row=0, column=0, padx=10, pady=10)
self.mood_slider = ctk.CTkSlider(self.notes_frame, from_=1, to=10, number_of_steps=9)
self.mood_slider.grid(row=0, column=1, padx=10, pady=10, sticky="ew")
self.mood_label = ctk.CTkLabel(self.notes_frame, text="5")
self.mood_label.grid(row=0, column=2, padx=5)
self.mood_slider.configure(command=lambda val: self.mood_label.configure(text=str(int(val))))
ctk.CTkLabel(self.notes_frame, text="Notes:").grid(row=1, column=0, padx=10, pady=10, sticky="n")
self.notes_entry = ctk.CTkTextbox(self.notes_frame, height=100)
self.notes_entry.grid(row=1, column=1, columnspan=2, padx=10, pady=10, sticky="ew")
# Save Button
self.save_btn = ctk.CTkButton(self, text="Save Daily Log", command=self.save_log)
self.save_btn.grid(row=4, column=0, columnspan=2, pady=20)
def load_data(self):
log = self.db.get_daily_log(self.date_str)
if log:
if log['sleep_hours'] is not None: self.sleep_entry.insert(0, format_float(log['sleep_hours']))
if log['sleep_quality']:
self.sleep_quality_slider.set(log['sleep_quality'])
self.sleep_quality_label.configure(text=str(log['sleep_quality']))
if log['weight'] is not None: self.weight_entry.insert(0, format_float(log['weight']))
if log['waist'] is not None: self.waist_entry.insert(0, format_float(log['waist']))
if log['mood']:
self.mood_slider.set(log['mood'])
self.mood_label.configure(text=str(log['mood']))
if log['notes']: self.notes_entry.insert("0.0", log['notes'])
def save_log(self):
# We allow entries even if some fields are empty (parse_float handles this)
# However, if they typed something invalid, we should catch it
s_val = self.sleep_entry.get()
w_val = self.weight_entry.get()
wa_val = self.waist_entry.get()
sleep = parse_float(s_val) if s_val else None
weight = parse_float(w_val) if w_val else None
waist = parse_float(wa_val) if wa_val else None
# Validation check: if they entered text but it parsed to None
if (s_val and sleep is None) or (w_val and weight is None) or (wa_val and waist is None):
messagebox.showerror("Error", "Please enter valid numbers (e.g. 75.5 or 75,5)")
return
data = {
'sleep_hours': sleep,
'sleep_quality': int(self.sleep_quality_slider.get()),
'weight': weight,
'waist': waist,
'mood': int(self.mood_slider.get()),
'notes': self.notes_entry.get("0.0", "end").strip()
}
self.db.save_daily_log(self.date_str, data)
self.save_btn.configure(text="Saved! (+20 XP)", fg_color="green")
self.db.add_xp(20)
self.gm.check_achievements()
# Cancel previous timer if exists
if hasattr(self, '_reset_btn_id'):
self.after_cancel(self._reset_btn_id)
self._reset_btn_id = self.after(2000, self.reset_save_button)
def reset_save_button(self):
if self.winfo_exists():
self.save_btn.configure(text="Save Daily Log", fg_color=["#3B8ED0", "#1F6AA5"])
def destroy(self):
if hasattr(self, '_reset_btn_id'):
self.after_cancel(self._reset_btn_id)
super().destroy()