HRV balanced dots, dashed gap lines, dashboard widgets + drag-to-edit layout
Build and push images / validate (push) Successful in 2s
Build and push images / build-backend (push) Successful in 6s
Build and push images / build-worker (push) Successful in 6s
Build and push images / build-frontend (push) Successful in 21s

- Green dots for balanced HRV (joining orange unbalanced / red low) on the trend
- Trend charts bridge data gaps with a dashed line instead of a blank break
- Dashboard: drop Health today; add VO2 max, small sleep, and HRV status widgets
- Dashboard: editable widget grid (react-grid-layout) with drag/resize, saved per-user

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:04:43 +01:00
parent af32a0bb7f
commit 8ed47d6042
7 changed files with 511 additions and 216 deletions
+17
View File
@@ -35,11 +35,16 @@ class ProfileOut(BaseModel):
goal_weight_kg: Optional[float]
estimated_max_hr: Optional[int]
is_admin: bool
dashboard_layout: Optional[list] = None
class Config:
from_attributes = True
class DashboardLayoutIn(BaseModel):
layout: Optional[list] = None # react-grid-layout array of {i,x,y,w,h}
def _estimated_max_hr(user: User) -> Optional[int]:
if user.birth_year:
return 220 - (datetime.now().year - user.birth_year)
@@ -53,6 +58,18 @@ async def get_profile(current_user: User = Depends(get_current_user)):
"estimated_max_hr": _estimated_max_hr(current_user)}
@router.put("/dashboard-layout")
async def save_dashboard_layout(
body: DashboardLayoutIn,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""Persist the user's customised dashboard widget layout."""
current_user.dashboard_layout = body.layout
await db.commit()
return {"status": "ok"}
@router.patch("/", response_model=ProfileOut)
async def update_profile(
body: ProfileUpdate,
+9
View File
@@ -100,6 +100,15 @@ async def init_db():
except Exception as e:
print(f"users.goal_weight_kg column migration skipped: {e}")
# dashboard_layout column on users added after initial creation
try:
async with engine.begin() as conn:
await conn.execute(text(
"ALTER TABLE users ADD COLUMN IF NOT EXISTS dashboard_layout JSON"
))
except Exception as e:
print(f"users.dashboard_layout column migration skipped: {e}")
# Backfill avg_hr_day / max_hr_day from intraday_hr for Garmin Connect synced days
try:
async with engine.begin() as conn:
+3
View File
@@ -37,6 +37,9 @@ class User(Base):
# Only PocketID users in this group may sign in. Null/blank = allow all.
pocketid_allowed_group = Column(String(128), nullable=True)
# Saved dashboard widget layout (react-grid-layout array). Null = use default.
dashboard_layout = Column(JSON, nullable=True)
activities = relationship("Activity", back_populates="user", cascade="all, delete-orphan")
health_metrics = relationship("HealthMetric", back_populates="user", cascade="all, delete-orphan")
named_routes = relationship("NamedRoute", back_populates="user", cascade="all, delete-orphan")