import { computed, shallowRef } from 'vue'; import { useEntity, useMutation, useQuery } from 'vue-sync-engine'; import { ProfileEntity, WeightEntity, daySummariesQuery, logWeightMutation, removeWeightMutation, weightsQuery, } from '../data/defs'; import type { DaySummary } from '../data/defs'; import { useEntities } from '../data/composables'; import { dayShort, dayTitle, lastDays, todayISO } from '../domain/dates'; import { fmtG, fmtKcal } from '../domain/format'; import { PROFILE_ID } from '../domain/types'; import type { WeightLog } from '../domain/types'; import { activeTab, selectedDate } from '../ui/state'; import { IconScale, IconTrash } from '../ui/icons'; const PERIODS = [7, 14, 30] as const; export default function StatsScreen() { const profile = useEntity(ProfileEntity, () => PROFILE_ID); const summariesQ = useQuery(daySummariesQuery, () => undefined); const weightsQ = useQuery(weightsQuery, () => undefined); const weights = useEntities(WeightEntity, () => weightsQ.data.value?.ids); const logWeight = useMutation(logWeightMutation); const removeWeight = useMutation(removeWeightMutation); const period = shallowRef<7 | 14 | 30>(14); const selectedBar = shallowRef(todayISO()); const target = computed(() => profile.value?.targetKcal ?? 2000); const days = computed(() => { const byDate = new Map((summariesQ.data.value ?? []).map(day => [day.date, day])); return lastDays(period.value).map(date => byDate.get(date) ?? { date, kcal: 0, protein: 0, fat: 0, carbs: 0, entries: 0 }, ); }); const chartMax = computed(() => { const peak = Math.max(target.value, ...days.value.map(day => day.kcal)); return peak * 1.08; }); const selectedDay = computed(() => days.value.find(day => day.date === selectedBar.value) ?? null, ); const tracked = computed(() => days.value.filter(day => day.entries > 0)); const averages = computed(() => { const list = tracked.value; if (list.length === 0) return null; const total = list.reduce( (acc, day) => ({ kcal: acc.kcal + day.kcal, protein: acc.protein + day.protein }), { kcal: 0, protein: 0 }, ); const onTarget = list.filter(day => day.kcal <= target.value).length; return { kcal: total.kcal / list.length, protein: total.protein / list.length, onTargetShare: Math.round((onTarget / list.length) * 100), }; }); // ── вес ─────────────────────────────────────────────────────────────────── const weightInput = shallowRef(0); const latestWeight = computed(() => weights.value.at(-1) ?? null); const weightDelta = computed(() => { const list = weights.value; const latest = list.at(-1); if (!latest || list.length < 2) return null; const weekAgoDate = lastDays(8)[0] ?? latest.date; const reference = [...list].reverse().find(item => item.date <= weekAgoDate) ?? list[0]; if (!reference || reference.id === latest.id) return null; return latest.kg - reference.kg; }); const sparkPoints = computed(() => { const list = weights.value.slice(-30); if (list.length < 2) return ''; const min = Math.min(...list.map(item => item.kg)); const max = Math.max(...list.map(item => item.kg)); const span = Math.max(max - min, 0.5); return list .map((item, index) => { const x = (index / (list.length - 1)) * 100; const y = 26 - ((item.kg - min) / span) * 22 + 2; return `${x.toFixed(1)},${y.toFixed(1)}`; }) .join(' '); }); const submitWeight = () => { if (weightInput.value <= 0) return; const date = todayISO(); const weight: WeightLog = { id: date, date, kg: weightInput.value, createdAt: Date.now() }; logWeight.mutate({ weight }); weightInput.value = 0; }; const openInDiary = (day: DaySummary) => { selectedDate.value = day.date; activeTab.value = 'diary'; }; const numeric = (raw: string): number => { const value = Number(raw.replace(',', '.')); return Number.isFinite(value) && value >= 0 ? value : 0; }; return (
Взвешивайтесь утром натощак. Смотрите на тренд за неделю, а не на ежедневные колебания — вода и еда в желудке шумят на ±1 кг.