"""Movement tab — today's exercise plan and logging.""" from datetime import date, datetime, timezone import gradio as gr from db import queries from vital_types.db import ExerciseLogEntry def _exercise_plan_summary() -> str: """Show framework exercise plan from settings.""" plan = queries.get_exercise_plan() if plan is None: return "_No exercise framework saved._" types = plan.get("types", []) types_text = ", ".join(types) if isinstance(types, list) else "—" return ( f"**Framework:** {plan.get('frequency', '—')}, {plan.get('intensity', '—')}, " f"{plan.get('session_duration_minutes', '—')} min\n\n" f"**Types:** {types_text}\n\n" f"**Avoid:** {', '.join(plan.get('avoid', [])) if isinstance(plan.get('avoid'), list) else '—'}\n\n" f"**Notes:** {plan.get('notes', '—')}" ) def _todays_exercise_block() -> str: """Show today's scheduled exercise from the daily plan.""" daily = queries.get_daily_plan(date.today()) if daily is None: return "_Today's schedule not generated yet._" exercise_jobs = [job for job in daily.jobs if job.type == "exercise"] prep_jobs = [ job for job in daily.jobs if job.type == "check_in" and job.job_id.startswith("exercise_prep_") ] if not exercise_jobs: return "_No exercise scheduled for today._" lines: list[str] = [] for job in exercise_jobs: prep = next( ( prep_job for prep_job in prep_jobs if prep_job.exercise_type == job.exercise_type or prep_job.job_id == f"exercise_prep_{job.job_id}" ), None, ) prep_line = "" if prep is not None: prep_line = f"\n\n**Heads-up at {prep.time}:** {prep.message}" lines.append( f"### {job.time} — {job.exercise_type or 'exercise'}\n" f"{job.message}\n\n" f"Duration: **{job.duration_minutes or '—'}** min" f"{prep_line}\n\n" f"_{job.context}_" ) return "\n\n".join(lines) def _exercise_log_text() -> str: """Format today's completed exercises.""" entries = queries.get_exercise_logs_for_date(date.today()) if not entries: return "_Nothing logged yet today._" lines: list[str] = [] for entry in entries: status = "done" if entry.completed else "pending" lines.append( f"- [{status}] {entry.exercise_type} — {entry.duration_minutes} min" ) return "\n".join(lines) def build_movement_tab() -> dict[str, gr.components.Component]: """Build the movement tab.""" gr.Markdown("## Movement") today_block = gr.Markdown(_todays_exercise_block()) framework = gr.Markdown(_exercise_plan_summary()) gr.Markdown("### Completed today") log_display = gr.Markdown(_exercise_log_text()) exercise_type = gr.Textbox(label="Exercise type", placeholder="walking") duration = gr.Number(label="Duration (minutes)", value=20, precision=0) log_status = gr.Markdown("") log_btn = gr.Button("Log it", variant="primary") refresh_btn = gr.Button("Refresh") def log_exercise(type_value: str, duration_value: float) -> tuple: """Log a completed exercise session.""" if not type_value.strip(): return gr.update(), "**Enter an exercise type.**" queries.insert_exercise_log( ExerciseLogEntry( date=date.today(), exercise_type=type_value.strip(), duration_minutes=max(1, int(duration_value)), completed=True, logged_at=datetime.now(timezone.utc), ) ) return gr.update(value=_exercise_log_text()), "**Exercise logged.**" log_btn.click( log_exercise, inputs=[exercise_type, duration], outputs=[log_display, log_status], ) refresh_btn.click( lambda: ( gr.update(value=_todays_exercise_block()), gr.update(value=_exercise_log_text()), ), outputs=[today_block, log_display], ) return {"today_block": today_block}