'use client'; import { useEffect, useState, type CSSProperties } from 'react'; import { calculateMetrics, defaultEntry, formatDuration, type SleepEntry } from '@/lib/sleep'; const dayFormatter = new Intl.DateTimeFormat('ru-RU', { day: 'numeric', month: 'long', year: 'numeric' }); function localDate(value: Date): string { const offset = value.getTimezoneOffset() * 60_000; return new Date(value.getTime() - offset).toISOString().slice(0, 10); } function shiftDate(date: string, days: number): string { const value = new Date(`${date}T12:00:00`); value.setDate(value.getDate() + days); return localDate(value); } export function SleepDiary() { const [date, setDate] = useState(() => localDate(new Date())); const [entry, setEntry] = useState(() => defaultEntry(localDate(new Date()))); const [status, setStatus] = useState<'loading' | 'idle' | 'saving' | 'saved' | 'error'>('loading'); useEffect(() => { let active = true; fetch(`/api/entries/${date}`) .then(async (response) => { if (!response.ok) throw new Error('Не удалось загрузить запись'); return response.json() as Promise<{ entry: SleepEntry | null }>; }) .then(({ entry: savedEntry }) => { if (!active) return; setEntry(savedEntry ?? defaultEntry(date)); setStatus('idle'); }) .catch(() => active && setStatus('error')); return () => { active = false; }; }, [date]); const metrics = calculateMetrics(entry); const filled = [entry.bedtime, entry.riseTime, entry.sleepLatencyMinutes, entry.awakenings, entry.wasoMinutes, entry.rating] .filter((value) => value !== '' && value !== null).length; function update(field: K, value: SleepEntry[K]) { setEntry((current) => ({ ...current, [field]: value })); setStatus('idle'); } async function save() { setStatus('saving'); try { const response = await fetch(`/api/entries/${date}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(entry), }); if (!response.ok) throw new Error(); const result = await response.json() as { entry: SleepEntry }; setEntry(result.entry); setStatus('saved'); } catch { setStatus('error'); } } return (

Sleepy

дневник сна

Дата записи

{dayFormatter.format(new Date(`${date}T12:00:00`))}

Заполнено: {filled} из 6. Достаточно, чтобы увидеть картину ночи.

{entry.napped &&
update('napStart', event.target.value || null)} /> update('napDurationMinutes', Number(event.target.value) || null)} />
}
update('bedtime', event.target.value)} /> update('sleepLatencyMinutes', Number(event.target.value))} />
{[5, 15, 30, 60, 90].map((minutes) => )}
{entry.awakenings}раз за ночь
update('wasoMinutes', Number(event.target.value))} />
update('riseTime', event.target.value)} />
{['совсем не спал(а)', 'плохо', 'терпимо', 'хорошо', 'отлично'].map((label, index) => { const value = index + 1; return ; })}