feat: enhance dashboard with PRs, adherence, activity, progression chart, and muscle heatmap
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 13s

Add 3 new stat cards (Last Workout, Personal Records, Adherence Rate),
recent activity table, progression timeline chart, and muscle group
recency heatmap to the dashboard. Remove Total Volume card.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 15:44:21 -05:00
parent c5a7728818
commit df8d5c65fb
8 changed files with 465 additions and 7 deletions

View File

@@ -7,12 +7,14 @@ and CSV export of workout history.
import csv
import io
import json
import os
import re
import tempfile
from datetime import date, timedelta
from typing import Optional
import structlog
from fastapi import APIRouter, Depends, Query, Request
from fastapi import APIRouter, Depends, File, Query, Request, UploadFile
from fastapi.responses import HTMLResponse, StreamingResponse
from sqlmodel import Session
@@ -21,6 +23,7 @@ from app.models.user import User
from app.services.analytics_service import AnalyticsService
from app.services.exercise_service import ExerciseService
from app.services.export_service import ExportService
from app.services.import_service import ImportService
from app.services.progression_service import ProgressionService
from app.utils.auth import require_active_profile
@@ -39,6 +42,11 @@ async def dashboard(
analytics = AnalyticsService(session)
stats = analytics.get_user_stats(profile.id)
volume_data = analytics.get_volume_by_day(profile.id)
personal_records = analytics.get_personal_records(profile.id)
adherence = analytics.get_adherence_rate(profile.id)
progression_timeline = analytics.get_progression_timeline(profile.id)
muscle_recency = analytics.get_muscle_group_recency(profile.id)
recent_activity = analytics.get_recent_activity(profile.id)
exercise_service = ExerciseService(session)
exercises = exercise_service.list_exercises()
@@ -52,6 +60,11 @@ async def dashboard(
"request": request,
"stats": stats,
"volume_data_json": json.dumps(volume_data),
"personal_records": personal_records,
"adherence": adherence,
"progression_timeline_json": json.dumps(progression_timeline),
"muscle_recency": muscle_recency,
"recent_activity": recent_activity,
"exercises": exercises,
"active_profile": profile,
"export_start_date": export_start,
@@ -100,6 +113,36 @@ async def export_csv(
)
@router.post("/import", response_class=HTMLResponse)
async def import_db(
request: Request,
db_file: UploadFile = File(...),
session: Session = Depends(get_db_session),
profile: User = Depends(require_active_profile),
):
"""Import workout history from an old SneakySwole database file."""
tmp_fd, tmp_path = tempfile.mkstemp(suffix=".db")
try:
with os.fdopen(tmp_fd, "wb") as tmp:
content = await db_file.read()
logger.info("import_upload_received", filename=db_file.filename, size=len(content))
tmp.write(content)
import_service = ImportService(session)
result = import_service.import_from_db(tmp_path)
except Exception:
logger.exception("import_failed")
raise
finally:
os.unlink(tmp_path)
templates = request.app.state.templates
return templates.TemplateResponse("partials/import_results.html", {
"request": request,
"result": result,
})
@router.get("/exercise/{exercise_id}", response_class=HTMLResponse)
async def exercise_progress(
exercise_id: int,