feat: Enhance medicines section with dose preview and quick add functionality

This commit is contained in:
William Valentin
2025-08-09 16:01:57 -07:00
parent 1613fb2625
commit 439204326b
+175 -5
View File
@@ -725,15 +725,77 @@ class UIManager:
scale.bind("<ButtonRelease-1>", _mk_scale_cmd(key, scale))
# Medicines section
# Medicines section (with dose preview and quick add)
ttk.Label(content, text="Medicines:").grid(row=row, column=0, sticky="w")
meds_frame = ttk.Frame(content)
meds_frame.grid(row=row, column=1, sticky="ew", padx=8, pady=4)
meds_frame.grid_columnconfigure(0, weight=1)
row += 1
# Helpers for dose formatting/parsing
def _format_storage_to_bullets(storage: str) -> str:
lines: list[str] = []
if not storage:
return "No doses recorded"
for token in str(storage).split("|"):
token = token.strip()
if not token:
continue
# Expect "YYYY-MM-DD HH:MM:SS:dose"
try:
ts_part, dose_part = token.rsplit(":", 1)
try:
ts = datetime.strptime(ts_part.strip(), "%Y-%m-%d %H:%M:%S")
except ValueError:
ts = datetime.fromisoformat(ts_part.strip())
time_str = ts.strftime("%I:%M %p").lstrip("0")
lines.append(f"{time_str} - {dose_part.strip()}")
except Exception:
# Fallback: keep raw
lines.append(token)
return "\n".join(lines) if lines else "No doses recorded"
def _build_storage_entry(date_s: str, time_s: str, dose_s: str) -> str:
# Parse date
try:
d = datetime.strptime(date_s, "%Y-%m-%d")
except ValueError:
try:
d = datetime.strptime(date_s, "%m/%d/%Y")
except ValueError:
d = datetime.fromisoformat(date_s)
# Parse time (prefer 24h, then 12h)
t_s = time_s.strip()
try:
t = datetime.strptime(t_s, "%H:%M")
except ValueError:
try:
t = datetime.strptime(t_s, "%I:%M %p")
except ValueError:
# If empty, use current time
now = datetime.now()
t = now.replace(second=0, microsecond=0)
stamp = d.replace(hour=t.hour, minute=t.minute, second=0, microsecond=0)
ts_str = stamp.strftime("%Y-%m-%d %H:%M:%S")
# Normalize dose string: append 'mg' if it looks numeric
ds = dose_s.strip()
if (
ds
and ds.replace(".", "", 1).isdigit()
and not ds.lower().endswith("mg")
):
ds = f"{ds}mg"
return f"{ts_str}:{ds}"
medicine_vars: dict[str, tk.IntVar] = {}
added_doses: dict[str, list[tuple[str, str]]] = {k: [] for k in medicine_keys}
dose_previews: dict[str, tk.StringVar] = {}
for i, key in enumerate(medicine_keys):
row_frame = ttk.Frame(meds_frame)
row_frame.grid(row=i, column=0, sticky="ew", pady=(0, 6))
row_frame.grid_columnconfigure(0, weight=1)
medicine_vars[key] = tk.IntVar(value=medicine_taken.get(key, 0))
med = self.medicine_manager.get_medicine(key)
text = (
@@ -742,12 +804,106 @@ class UIManager:
else key.capitalize()
)
chk = ttk.Checkbutton(
meds_frame,
row_frame,
text=text,
variable=medicine_vars[key],
style="Modern.TCheckbutton",
)
chk.grid(row=i, column=0, sticky="w", padx=2, pady=2)
chk.grid(row=0, column=0, sticky="w")
# Dose preview + quick add panel
preview_var = tk.StringVar(
value=_format_storage_to_bullets(medicine_doses_str.get(key, ""))
)
dose_previews[key] = preview_var
panel = ttk.Frame(row_frame)
panel.grid(row=1, column=0, sticky="ew", padx=18)
panel.grid_columnconfigure(1, weight=1)
ttk.Label(panel, text="Doses:").grid(row=0, column=0, sticky="nw")
ttk.Label(panel, textvariable=preview_var, justify="left").grid(
row=0, column=1, sticky="ew"
)
# Add controls
add_row = ttk.Frame(panel)
add_row.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(4, 0))
ttk.Label(add_row, text="Time (HH:MM or 12h):").grid(
row=0, column=0, sticky="w"
)
time_var = tk.StringVar()
ttk.Entry(add_row, textvariable=time_var, width=10).grid(
row=0, column=1, sticky="w", padx=(4, 8)
)
ttk.Label(add_row, text="Dose:").grid(row=0, column=2, sticky="w")
dose_var = tk.StringVar()
ttk.Entry(add_row, textvariable=dose_var, width=10).grid(
row=0, column=3, sticky="w", padx=(4, 8)
)
def _do_add(
k: str, t_v: tk.StringVar, d_v: tk.StringVar, pv: tk.StringVar
) -> None:
t_s = t_v.get().strip()
d_s = d_v.get().strip()
if not d_s:
return
# Record added dose; preview shows bullet line
added_doses[k].append((t_s or datetime.now().strftime("%H:%M"), d_s))
# Update preview
try:
entry = _build_storage_entry(
date_var.get(), t_s or datetime.now().strftime("%H:%M"), d_s
)
new_preview = pv.get()
as_bullet = _format_storage_to_bullets(entry)
new_preview = (
"No doses recorded"
if not new_preview
or new_preview.startswith("No doses recorded")
else new_preview
)
pv.set(
as_bullet
if new_preview == "No doses recorded"
else f"{new_preview}\n{as_bullet}"
)
except Exception:
# Best-effort UI update; save path will still append robustly
pass
# Clear inputs for next add
t_v.set("")
d_v.set("")
ttk.Button(
add_row,
text="Add",
command=lambda k=key, tv=time_var, dv=dose_var, pv=preview_var: _do_add(
k, tv, dv, pv
),
style="Action.TButton",
).grid(row=0, column=4, sticky="w")
# Quick dose shortcuts
qd = self.medicine_manager.get_quick_doses(key)
if qd:
qrow = ttk.Frame(panel)
qrow.grid(row=2, column=0, columnspan=2, sticky="w", pady=(2, 0))
ttk.Label(qrow, text="Quick:").grid(row=0, column=0, sticky="w")
for j, amt in enumerate(qd):
ttk.Button(
qrow,
text=f"{amt}mg",
command=(
lambda k=key, a=amt, tv=time_var, pv=preview_var: _do_add(
k,
tv,
tk.StringVar(value=a),
pv,
)
),
).grid(row=0, column=j + 1, padx=2)
# Note field
ttk.Label(content, text="Note:").grid(row=row, column=0, sticky="nw")
@@ -772,8 +928,22 @@ class UIManager:
args.append(int(medicine_vars[key].get()))
# Note
args.append(note_var.get())
# Preserve existing dose strings unless caller offers an editor elsewhere
dose_map = {k: medicine_doses_str.get(k, "") for k in medicine_keys}
# Merge any newly added doses into existing strings
dose_map: dict[str, str] = {}
for k in medicine_keys:
base = medicine_doses_str.get(k, "").strip()
new_entries = [
_build_storage_entry(date_var.get(), t, d)
for (t, d) in added_doses.get(k, [])
]
merged = base
if new_entries:
merged = (
f"{base}|{'|'.join(new_entries)}"
if base
else "|".join(new_entries)
)
dose_map[k] = merged
args.append(dose_map)
with suppress(Exception):
callbacks.get("save")(win, *args)