feat: Enhance medicines section with dose preview and quick add functionality
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user