feat: update versioning and add activity last updated endpoint
- 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:
parent
5d67a77a12
commit
ba2bd3a4a2
|
|
@ -59,6 +59,15 @@ def _last_activity_date(profile_id: str) -> Optional[str]:
|
||||||
return _iso(row["d"])
|
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]:
|
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).
|
Bundle für Fitness-Übersicht: KPI-Kacheln + eingebettete Chart-Payloads (Chart.js-Format).
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ from data_layer.body_metrics import (
|
||||||
)
|
)
|
||||||
from data_layer.body_viz import get_body_history_viz_bundle
|
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.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.recovery_viz import get_recovery_dashboard_viz_bundle
|
||||||
from data_layer.history_overview_viz import get_history_overview_viz_bundle
|
from data_layer.history_overview_viz import get_history_overview_viz_bundle
|
||||||
from data_layer.recovery_chart_payloads import (
|
from data_layer.recovery_chart_payloads import (
|
||||||
|
|
@ -319,6 +319,17 @@ def get_fitness_dashboard_viz(
|
||||||
return serialize_dates(bundle)
|
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")
|
@router.get("/recovery-dashboard-viz")
|
||||||
def get_recovery_dashboard_viz(
|
def get_recovery_dashboard_viz(
|
||||||
days: int = Query(
|
days: int = Query(
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ Semantic Versioning: MAJOR.MINOR.PATCH
|
||||||
- PATCH: Bugfix, kleine Änderung, Refactor
|
- PATCH: Bugfix, kleine Änderung, Refactor
|
||||||
"""
|
"""
|
||||||
|
|
||||||
APP_VERSION = "0.9q"
|
APP_VERSION = "0.9r"
|
||||||
BUILD_DATE = "2026-04-11"
|
BUILD_DATE = "2026-04-20"
|
||||||
DB_SCHEMA_VERSION = "20260409c" # 048/049 vitals_baseline.source csv + SAVEPOINT Import
|
DB_SCHEMA_VERSION = "20260409c" # 048/049 vitals_baseline.source csv + SAVEPOINT Import
|
||||||
|
|
||||||
MODULE_VERSIONS = {
|
MODULE_VERSIONS = {
|
||||||
|
|
@ -36,6 +36,14 @@ MODULE_VERSIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
CHANGELOG = [
|
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",
|
"version": "0.9q",
|
||||||
"date": "2026-04-11",
|
"date": "2026-04-11",
|
||||||
|
|
|
||||||
|
|
@ -1196,14 +1196,12 @@ function NutritionSection({ profile, insights, onRequest, loadingSlug, filterAct
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Activity Section — nur Layer-2b-Bundle (+ KI-Insights), keine parallelen Client-Charts ─
|
// ── 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 [period, setPeriod] = useState(30)
|
||||||
const actList = activities || []
|
|
||||||
const hasList = actList.length > 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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}/>
|
<PeriodSelector value={period} onChange={setPeriod}/>
|
||||||
<p style={{ fontSize: 11, color: 'var(--text3)', lineHeight: 1.45, marginBottom: 10 }}>
|
<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.
|
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>
|
</div>
|
||||||
<RecoveryDashboardOverview period={period} onPeriodChange={setPeriod} hidePeriodSelector />
|
<RecoveryDashboardOverview period={period} onPeriodChange={setPeriod} hidePeriodSelector />
|
||||||
|
|
||||||
{hasList && globalQualityLevel && globalQualityLevel !== 'all' && (
|
{activityLastDate && globalQualityLevel && globalQualityLevel !== 'all' && (
|
||||||
<div style={{
|
<div style={{
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
marginBottom: 12, padding:'8px 12px', borderRadius:8,
|
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 { activeProfile } = useProfile() // Issue #31: Get global quality filter
|
||||||
const location = useLocation?.() || {}
|
const location = useLocation?.() || {}
|
||||||
const [tab, setTab] = useState((location.state?.tab) || 'overview')
|
const [tab, setTab] = useState((location.state?.tab) || 'overview')
|
||||||
const [weights, setWeights] = useState([])
|
const [activityLastDate, setActivityLastDate] = useState(null)
|
||||||
const [calipers, setCalipers] = useState([])
|
|
||||||
const [circs, setCircs] = useState([])
|
|
||||||
const [nutrition, setNutrition] = useState([])
|
|
||||||
const [activities, setActivities] = useState([])
|
|
||||||
const [insights, setInsights] = useState([])
|
const [insights, setInsights] = useState([])
|
||||||
const [prompts, setPrompts] = useState([])
|
const [prompts, setPrompts] = useState([])
|
||||||
const [profile, setProfile] = useState(null)
|
const [profile, setProfile] = useState(null)
|
||||||
|
|
@ -1747,15 +1741,15 @@ export default function History() {
|
||||||
const [loadingSlug,setLoadingSlug]= useState(null)
|
const [loadingSlug,setLoadingSlug]= useState(null)
|
||||||
|
|
||||||
const loadAll = () => Promise.all([
|
const loadAll = () => Promise.all([
|
||||||
api.listWeight(365), api.listCaliper(), api.listCirc(),
|
api.latestInsights(),
|
||||||
api.listNutrition(90), api.listActivity(25_000),
|
api.getProfile(),
|
||||||
api.latestInsights(), api.getProfile(),
|
|
||||||
api.listPrompts(),
|
api.listPrompts(),
|
||||||
]).then(([w,ca,ci,n,a,ins,p,pr])=>{
|
api.getActivityLastUpdated(),
|
||||||
setWeights(w); setCalipers(ca); setCircs(ci)
|
]).then(([ins, p, pr, actMeta]) => {
|
||||||
setNutrition(n); setActivities(a)
|
setInsights(Array.isArray(ins) ? ins : [])
|
||||||
setInsights(Array.isArray(ins)?ins:[]); setProfile(p)
|
setProfile(p)
|
||||||
setPrompts(Array.isArray(pr)?pr:[])
|
setPrompts(Array.isArray(pr) ? pr : [])
|
||||||
|
setActivityLastDate(actMeta?.last_activity_date ?? null)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -1820,7 +1814,7 @@ export default function History() {
|
||||||
{tab==='overview' && <HistoryOverviewSection {...sp}/>}
|
{tab==='overview' && <HistoryOverviewSection {...sp}/>}
|
||||||
{tab==='body' && <BodySection profile={profile} {...sp}/>}
|
{tab==='body' && <BodySection profile={profile} {...sp}/>}
|
||||||
{tab==='nutrition' && <NutritionSection 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/>}
|
{tab==='photos' && <PhotoGrid/>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -644,6 +644,8 @@ export const api = {
|
||||||
/** Layer 2b: Erholung — KPI, Insights, Charts R1–R5 (recovery_metrics) */
|
/** Layer 2b: Erholung — KPI, Insights, Charts R1–R5 (recovery_metrics) */
|
||||||
getRecoveryDashboardViz: (days=28) => req(`/charts/recovery-dashboard-viz?days=${days}`),
|
getRecoveryDashboardViz: (days=28) => req(`/charts/recovery-dashboard-viz?days=${days}`),
|
||||||
getHistoryOverviewViz: (days=30) => req(`/charts/history-overview-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}`),
|
getWeightEnergyCorrelationChart: (maxLag=14) => req(`/charts/weight-energy-correlation?max_lag=${maxLag}`),
|
||||||
getLbmProteinCorrelationChart: (maxLag=14) => req(`/charts/lbm-protein-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}`),
|
getLoadVitalsCorrelationChart: (maxLag=14) => req(`/charts/load-vitals-correlation?max_lag=${maxLag}`),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user