feat: update versioning and add activity last updated endpoint
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Build Test / pytest-backend (push) Successful in 5s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 16s

- Bumped application version to 0.9r and updated build date to 2026-04-20.
- Added a new endpoint `/activity-last-updated` to retrieve the last activity date for a user, optimizing data retrieval for activity history.
- Updated the frontend to utilize the new endpoint, enhancing the ActivitySection with the last activity date display.
- Refactored the History component to streamline data loading and improve user experience with activity insights.
This commit is contained in:
Lars 2026-04-20 14:43:31 +02:00
parent 5d67a77a12
commit ba2bd3a4a2
5 changed files with 46 additions and 22 deletions

View File

@ -59,6 +59,15 @@ def _last_activity_date(profile_id: str) -> Optional[str]:
return _iso(row["d"])
def get_activity_last_updated_iso(profile_id: str) -> Optional[str]:
"""
Leichtgewicht: letztes activity_log.date identisch zu ``last_updated`` im Fitness-Viz-Bundle.
Für History-Header o. Ä. ohne vollständige Aktivitätsliste (Phase A, Issue-53-Pfad).
"""
return _last_activity_date(profile_id)
def get_fitness_dashboard_viz_bundle(profile_id: str, days: int) -> Dict[str, Any]:
"""
Bundle für Fitness-Übersicht: KPI-Kacheln + eingebettete Chart-Payloads (Chart.js-Format).

View File

@ -33,7 +33,7 @@ from data_layer.body_metrics import (
)
from data_layer.body_viz import get_body_history_viz_bundle
from data_layer.nutrition_viz import get_nutrition_history_viz_bundle
from data_layer.fitness_viz import get_fitness_dashboard_viz_bundle
from data_layer.fitness_viz import get_fitness_dashboard_viz_bundle, get_activity_last_updated_iso
from data_layer.recovery_viz import get_recovery_dashboard_viz_bundle
from data_layer.history_overview_viz import get_history_overview_viz_bundle
from data_layer.recovery_chart_payloads import (
@ -319,6 +319,17 @@ def get_fitness_dashboard_viz(
return serialize_dates(bundle)
@router.get("/activity-last-updated")
def get_activity_last_updated(session: dict = Depends(require_auth)) -> Dict:
"""
Minimal-Metadatum: letztes Trainingsdatum gleiche Quelle wie ``last_updated`` im Fitness-Viz-Bundle.
Vermeidet Massen-Ladevorgänge (z. B. listActivity) nur für Datumsanzeige im Verlauf.
"""
pid = session["profile_id"]
return {"last_activity_date": get_activity_last_updated_iso(pid)}
@router.get("/recovery-dashboard-viz")
def get_recovery_dashboard_viz(
days: int = Query(

View File

@ -7,8 +7,8 @@ Semantic Versioning: MAJOR.MINOR.PATCH
- PATCH: Bugfix, kleine Änderung, Refactor
"""
APP_VERSION = "0.9q"
BUILD_DATE = "2026-04-11"
APP_VERSION = "0.9r"
BUILD_DATE = "2026-04-20"
DB_SCHEMA_VERSION = "20260409c" # 048/049 vitals_baseline.source csv + SAVEPOINT Import
MODULE_VERSIONS = {
@ -36,6 +36,14 @@ MODULE_VERSIONS = {
}
CHANGELOG = [
{
"version": "0.9r",
"date": "2026-04-20",
"changes": [
"History Phase A: GET /api/charts/activity-last-updated (data_layer fitness_viz, gleiche Quelle wie last_updated im Fitness-Bundle)",
"History: entfernt toten Initial-Load listWeight/listCaliper/listCirc/listNutrition/listActivity(25k); Profil/Insights/Prompts + Activity-Datum",
],
},
{
"version": "0.9q",
"date": "2026-04-11",

View File

@ -1196,14 +1196,12 @@ function NutritionSection({ profile, insights, onRequest, loadingSlug, filterAct
}
// Activity Section nur Layer-2b-Bundle (+ KI-Insights), keine parallelen Client-Charts
function ActivitySection({ activities, insights, onRequest, loadingSlug, filterActiveSlugs, globalQualityLevel }) {
function ActivitySection({ activityLastDate, insights, onRequest, loadingSlug, filterActiveSlugs, globalQualityLevel }) {
const [period, setPeriod] = useState(30)
const actList = activities || []
const hasList = actList.length > 0
return (
<div>
<SectionHeader title="🏋️ Fitness" to="/activity" toLabel="Alle Einträge" lastUpdated={actList[0]?.date}/>
<SectionHeader title="🏋️ Fitness" to="/activity" toLabel="Alle Einträge" lastUpdated={activityLastDate || undefined}/>
<PeriodSelector value={period} onChange={setPeriod}/>
<p style={{ fontSize: 11, color: 'var(--text3)', lineHeight: 1.45, marginBottom: 10 }}>
Fitness und Erholung aus den Data-Layer-Bundles (Issue 53). Zeitraum-Buttons steuern beide Bereiche gleichzeitig.
@ -1215,7 +1213,7 @@ function ActivitySection({ activities, insights, onRequest, loadingSlug, filterA
</div>
<RecoveryDashboardOverview period={period} onPeriodChange={setPeriod} hidePeriodSelector />
{hasList && globalQualityLevel && globalQualityLevel !== 'all' && (
{activityLastDate && globalQualityLevel && globalQualityLevel !== 'all' && (
<div style={{
marginTop: 12,
marginBottom: 12, padding:'8px 12px', borderRadius:8,
@ -1735,11 +1733,7 @@ export default function History() {
const { activeProfile } = useProfile() // Issue #31: Get global quality filter
const location = useLocation?.() || {}
const [tab, setTab] = useState((location.state?.tab) || 'overview')
const [weights, setWeights] = useState([])
const [calipers, setCalipers] = useState([])
const [circs, setCircs] = useState([])
const [nutrition, setNutrition] = useState([])
const [activities, setActivities] = useState([])
const [activityLastDate, setActivityLastDate] = useState(null)
const [insights, setInsights] = useState([])
const [prompts, setPrompts] = useState([])
const [profile, setProfile] = useState(null)
@ -1747,15 +1741,15 @@ export default function History() {
const [loadingSlug,setLoadingSlug]= useState(null)
const loadAll = () => Promise.all([
api.listWeight(365), api.listCaliper(), api.listCirc(),
api.listNutrition(90), api.listActivity(25_000),
api.latestInsights(), api.getProfile(),
api.latestInsights(),
api.getProfile(),
api.listPrompts(),
]).then(([w,ca,ci,n,a,ins,p,pr])=>{
setWeights(w); setCalipers(ca); setCircs(ci)
setNutrition(n); setActivities(a)
setInsights(Array.isArray(ins)?ins:[]); setProfile(p)
setPrompts(Array.isArray(pr)?pr:[])
api.getActivityLastUpdated(),
]).then(([ins, p, pr, actMeta]) => {
setInsights(Array.isArray(ins) ? ins : [])
setProfile(p)
setPrompts(Array.isArray(pr) ? pr : [])
setActivityLastDate(actMeta?.last_activity_date ?? null)
setLoading(false)
})
@ -1820,7 +1814,7 @@ export default function History() {
{tab==='overview' && <HistoryOverviewSection {...sp}/>}
{tab==='body' && <BodySection profile={profile} {...sp}/>}
{tab==='nutrition' && <NutritionSection profile={profile} {...sp}/>}
{tab==='activity' && <ActivitySection activities={activities} globalQualityLevel={activeProfile?.quality_filter_level} {...sp}/>}
{tab==='activity' && <ActivitySection activityLastDate={activityLastDate} globalQualityLevel={activeProfile?.quality_filter_level} {...sp}/>}
{tab==='photos' && <PhotoGrid/>}
</div>
</div>

View File

@ -644,6 +644,8 @@ export const api = {
/** Layer 2b: Erholung — KPI, Insights, Charts R1R5 (recovery_metrics) */
getRecoveryDashboardViz: (days=28) => req(`/charts/recovery-dashboard-viz?days=${days}`),
getHistoryOverviewViz: (days=30) => req(`/charts/history-overview-viz?days=${days}`),
/** Minimal: letztes activity_log.date — wie fitness-dashboard-viz.last_updated */
getActivityLastUpdated: () => req('/charts/activity-last-updated'),
getWeightEnergyCorrelationChart: (maxLag=14) => req(`/charts/weight-energy-correlation?max_lag=${maxLag}`),
getLbmProteinCorrelationChart: (maxLag=14) => req(`/charts/lbm-protein-correlation?max_lag=${maxLag}`),
getLoadVitalsCorrelationChart: (maxLag=14) => req(`/charts/load-vitals-correlation?max_lag=${maxLag}`),