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))
|
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")
|
ttk.Label(content, text="Medicines:").grid(row=row, column=0, sticky="w")
|
||||||
meds_frame = ttk.Frame(content)
|
meds_frame = ttk.Frame(content)
|
||||||
meds_frame.grid(row=row, column=1, sticky="ew", padx=8, pady=4)
|
meds_frame.grid(row=row, column=1, sticky="ew", padx=8, pady=4)
|
||||||
meds_frame.grid_columnconfigure(0, weight=1)
|
meds_frame.grid_columnconfigure(0, weight=1)
|
||||||
row += 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] = {}
|
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):
|
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))
|
medicine_vars[key] = tk.IntVar(value=medicine_taken.get(key, 0))
|
||||||
med = self.medicine_manager.get_medicine(key)
|
med = self.medicine_manager.get_medicine(key)
|
||||||
text = (
|
text = (
|
||||||
@@ -742,12 +804,106 @@ class UIManager:
|
|||||||
else key.capitalize()
|
else key.capitalize()
|
||||||
)
|
)
|
||||||
chk = ttk.Checkbutton(
|
chk = ttk.Checkbutton(
|
||||||
meds_frame,
|
row_frame,
|
||||||
text=text,
|
text=text,
|
||||||
variable=medicine_vars[key],
|
variable=medicine_vars[key],
|
||||||
style="Modern.TCheckbutton",
|
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
|
# Note field
|
||||||
ttk.Label(content, text="Note:").grid(row=row, column=0, sticky="nw")
|
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()))
|
args.append(int(medicine_vars[key].get()))
|
||||||
# Note
|
# Note
|
||||||
args.append(note_var.get())
|
args.append(note_var.get())
|
||||||
# Preserve existing dose strings unless caller offers an editor elsewhere
|
# Merge any newly added doses into existing strings
|
||||||
dose_map = {k: medicine_doses_str.get(k, "") for k in medicine_keys}
|
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)
|
args.append(dose_map)
|
||||||
with suppress(Exception):
|
with suppress(Exception):
|
||||||
callbacks.get("save")(win, *args)
|
callbacks.get("save")(win, *args)
|
||||||
|
|||||||
Reference in New Issue
Block a user