import { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; import { apiClient, errorMessage, HealthDay } from '../services/api'; import Chart from '../components/charts/Chart'; import StatTile, { Status } from '../components/charts/StatTile'; import './Pages.css'; const DAYS = 30; /** MM-DD keeps the axis readable at 30 points. */ const short = (iso: string) => iso.slice(5); function avg(values: Array): number | null { const present = values.filter((v): v is number => v != null); if (!present.length) return null; return present.reduce((a, b) => a + b, 0) / present.length; } /* Thresholds follow the same rules the recommendation engine uses, so the dashboard and the advice never disagree about what counts as low. */ function sleepStatus(hours: number | null): [Status, string] | [] { if (hours == null) return []; if (hours < 6) return ['critical', '偏少']; if (hours < 7) return ['warning', '略少']; return ['good', '充足']; } function rhrStatus(bpm: number | null): [Status, string] | [] { if (bpm == null) return []; if (bpm > 70) return ['serious', '偏高']; if (bpm > 65) return ['warning', '略高']; return ['good', '正常']; } function Dashboard() { const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [days, setDays] = useState([]); useEffect(() => { const load = async () => { try { const end = new Date(); const start = new Date(end.getTime() - (DAYS - 1) * 86400000); setDays( await apiClient.getHealthSummary( start.toISOString().slice(0, 10), end.toISOString().slice(0, 10) ) ); } catch (err: any) { setError(errorMessage(err, '加载数据失败')); } finally { setLoading(false); } }; load(); }, []); if (loading) return
加载中…
; if (error) { return (

今日概览

{error}
); } if (days.length === 0) { return (

今日概览

还没有任何健康数据。

去同步 Garmin 数据
); } const today = days[days.length - 1]; const rows = days.map((d) => ({ ...d, date: short(d.date) })); const avgSteps = avg(days.map((d) => d.steps)); const avgSleep = avg(days.map((d) => d.sleepDuration)); const avgRhr = avg(days.map((d) => d.heartRate)); const avgHrv = avg(days.map((d) => d.heartRateVariability)); const [sleepTone, sleepWord] = sleepStatus(today.sleepDuration); const [rhrTone, rhrWord] = rhrStatus(today.heartRate); const round = (v: number | null, d = 0) => v == null ? null : Math.round(v * 10 ** d) / 10 ** d; return (

今日概览

{today.date} · 近 {days.length} 天数据

查看全部趋势 →
{/* Activity ---------------------------------------------------------- */}

活动

{/* Heart & stress ---------------------------------------------------- */}

心率与压力

{/* Sleep & breathing -------------------------------------------------- */}

睡眠与呼吸

查看睡眠分期详情 →

{/* Trends ------------------------------------------------------------- */}

近 {days.length} 天趋势

); } export default Dashboard;