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 (

Статистика

{PERIODS.map(value => ( ))}
{/* График калорий по дням */}

Калории по дням

{`цель ${fmtKcal(target.value)}`}
{selectedDay.value && ( )}
{/* Линия цели */}
{days.value.map(day => ( ))}
{dayShort(days.value[0]?.date ?? todayISO())} {dayShort(days.value.at(-1)?.date ?? todayISO())}
{/* Средние за период */} {averages.value && (
{fmtKcal(averages.value.kcal)}
ккал в среднем
{fmtG(averages.value.protein)}
белка в день, г
{averages.value.onTargetShare} %
дней в цели
)} {/* Вес */}

Вес

{weightDelta.value !== null && ( {weightDelta.value > 0 ? '+' : ''} {fmtG(weightDelta.value)} {' '} кг за неделю )}
{latestWeight.value && (
{fmtG(latestWeight.value.kg)} кг
{sparkPoints.value && ( )}
)}
(weightInput.value = numeric(event.currentTarget.value))} class="w-full flex-1 rounded-xl border hairline bg-raised/70 px-3.5 py-2.5 text-[16px] text-ink outline-none transition focus:border-ember/50 placeholder:text-ink-faint tnum" />
{weights.value.length > 0 && (
{[...weights.value].reverse().slice(0, 5).map(item => (
{dayTitle(item.date)} {fmtG(item.kg)} {' '} кг
))}
)}

Взвешивайтесь утром натощак. Смотрите на тренд за неделю, а не на ежедневные колебания — вода и еда в желудке шумят на ±1 кг.

); }