diff --git a/backend/routers/activity.py b/backend/routers/activity.py index c3b57a0..b2e2009 100644 --- a/backend/routers/activity.py +++ b/backend/routers/activity.py @@ -9,7 +9,7 @@ import uuid import logging from typing import Optional -from fastapi import APIRouter, HTTPException, UploadFile, File, Header, Depends +from fastapi import APIRouter, HTTPException, UploadFile, File, Header, Depends, Query from db import get_db, get_cursor, r2d from auth import require_auth, check_feature_access, increment_feature_usage @@ -32,24 +32,45 @@ logger = logging.getLogger(__name__) @router.get("") -def list_activity(limit: int=200, x_profile_id: Optional[str]=Header(default=None), session: dict=Depends(require_auth)): - """Get activity entries for current profile.""" +def list_activity( + limit: int = Query(200, ge=1, le=50_000), + days: Optional[int] = Query(None, ge=1, le=4000, description="Nur Einträge mit date >= HEUTE − days (Kalendertage)"), + x_profile_id: Optional[str] = Header(default=None), + session: dict = Depends(require_auth), +): + """Get activity entries for current profile. Optional *days* filter by calendar window (not the same as *limit*).""" pid = get_pid(x_profile_id) with get_db() as conn: cur = get_cursor(conn) - # Issue #31: Apply global quality filter + # Issue #31: Apply global quality filter (profile from DB = saved level) cur.execute("SELECT * FROM profiles WHERE id=%s", (pid,)) profile = r2d(cur.fetchone()) quality_filter = get_quality_filter_sql(profile) - cur.execute(f""" - SELECT * FROM activity_log - WHERE profile_id=%s - {quality_filter} - ORDER BY date DESC, start_time DESC - LIMIT %s - """, (pid, limit)) + if days is not None: + cur.execute( + f""" + SELECT * FROM activity_log + WHERE profile_id=%s + {quality_filter} + AND date >= (CURRENT_DATE - %s::integer) + ORDER BY date DESC, start_time DESC + LIMIT %s + """, + (pid, days, limit), + ) + else: + cur.execute( + f""" + SELECT * FROM activity_log + WHERE profile_id=%s + {quality_filter} + ORDER BY date DESC, start_time DESC + LIMIT %s + """, + (pid, limit), + ) return [r2d(r) for r in cur.fetchall()] diff --git a/backend/version.py b/backend/version.py index 5f465cd..b9fc054 100644 --- a/backend/version.py +++ b/backend/version.py @@ -19,7 +19,7 @@ MODULE_VERSIONS = { "weight": "1.0.3", "circumference": "1.0.1", "caliper": "1.0.1", - "activity": "1.1.0", + "activity": "1.2.0", # GET /activity: optional days= window + limit "nutrition": "1.0.2", "photos": "1.0.0", "insights": "1.3.0", diff --git a/frontend/src/components/TrainingTypeDistribution.jsx b/frontend/src/components/TrainingTypeDistribution.jsx index 0c07780..271c3fc 100644 --- a/frontend/src/components/TrainingTypeDistribution.jsx +++ b/frontend/src/components/TrainingTypeDistribution.jsx @@ -15,8 +15,10 @@ export default function TrainingTypeDistribution({ days = 28 }) { const [loading, setLoading] = useState(true) useEffect(() => { + const safeDays = Math.max(1, Math.min(4000, Number(days) || 28)) + const limit = Math.min(50_000, Math.max(250, safeDays * 25)) Promise.all([ - api.listActivity(days), + api.listActivity(limit, safeDays), api.getTrainingCategories() ]).then(([activities, cats]) => { setCategories(cats) @@ -43,7 +45,7 @@ export default function TrainingTypeDistribution({ days = 28 }) { console.error('Failed to load training type distribution:', err) setLoading(false) }) - }, [days, activeProfile?.quality_filter_level]) // Issue #31: Reload when quality filter changes + }, [days, activeProfile?.quality_filter_level, activeProfile?.id]) if (loading) { return ( diff --git a/frontend/src/components/pilot/PilotActivitySection.jsx b/frontend/src/components/pilot/PilotActivitySection.jsx index f934d05..f4e23a6 100644 --- a/frontend/src/components/pilot/PilotActivitySection.jsx +++ b/frontend/src/components/pilot/PilotActivitySection.jsx @@ -21,8 +21,8 @@ export default function PilotActivitySection({ refreshTick = 0, chartDays = BODY let cancelled = false ;(async () => { try { - const fetchDays = Math.max(120, periodDays + 60) - const a = await api.listActivity(fetchDays) + const limit = Math.min(50_000, Math.max(200, periodDays * 25)) + const a = await api.listActivity(limit, periodDays) if (!cancelled) setActivities(Array.isArray(a) ? a : []) } catch { if (!cancelled) setActivities([]) diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index f8b4dd8..0996931 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -276,7 +276,7 @@ export default function Dashboard() { api.listCaliper(3), api.listCirc(2), api.listNutrition(30), - api.listActivity(30), + api.listActivity(800, 30), api.latestInsights(), ]).then(([s,w,ca,ci,n,a,ins])=>{ setStats(s); setWeights(w); setCalipers(ca); setCircs(ci) diff --git a/frontend/src/pages/History.jsx b/frontend/src/pages/History.jsx index 108c7b9..c0dc264 100644 --- a/frontend/src/pages/History.jsx +++ b/frontend/src/pages/History.jsx @@ -972,7 +972,7 @@ export default function History() { const loadAll = () => Promise.all([ api.listWeight(365), api.listCaliper(), api.listCirc(), - api.listNutrition(90), api.listActivity(200), + api.listNutrition(90), api.listActivity(25_000), api.nutritionCorrelations(), api.latestInsights(), api.getProfile(), api.listPrompts(), ]).then(([w,ca,ci,n,a,corr,ins,p,pr])=>{ @@ -983,7 +983,9 @@ export default function History() { setLoading(false) }) - useEffect(()=>{ loadAll() },[]) + useEffect(() => { + loadAll() + }, [activeProfile?.quality_filter_level]) useEffect(() => { const t = location.state?.tab diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index ad04e3b..7782195 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -118,7 +118,12 @@ export const api = { deleteCaliper: (id) => req(`/caliper/${id}`,{method:'DELETE'}), // Activity - listActivity: (l=200)=> req(`/activity?limit=${l}`), + /** @param {number} [limit=200] @param {number} [days] nur Einträge ab HEUTE−days (Kalendertage), backend-filtert */ + listActivity: (limit=200, days)=> { + const q = new URLSearchParams({ limit: String(limit) }) + if (days != null && days !== '') q.set('days', String(days)) + return req(`/activity?${q}`) + }, createActivity: (d) => req('/activity',json(d)), updateActivity: (id,d) => req(`/activity/${id}`,jput(d)), deleteActivity: (id) => req(`/activity/${id}`,{method:'DELETE'}),