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
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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user