'use client' import { useEffect, useState } from 'react' import { motion, AnimatePresence } from 'framer-motion' import { X, AlarmClock, Trash2 } from 'lucide-react' interface Timer { id: string label: string startedAt: string endsAt: string } type Mode = | { type: 'control'; timer: Timer } | { type: 'create' } export interface TimerModalProps { mode: Mode | null onClose: () => void } function formatBig(ms: number): string { if (ms <= 0) return '0:00' const total = Math.ceil(ms / 1000) const h = Math.floor(total / 3600) const m = Math.floor((total % 3600) / 60) const s = total % 60 if (h > 0) return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}` return `${m}:${s.toString().padStart(2, '0')}` } async function postTimer(body: any) { return fetch('/api/voice/timer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), credentials: 'include', }) } const ADJUST_OPTIONS = [ { label: '−5 мин', delta: -300 }, { label: '−1 мин', delta: -60 }, { label: '−10 сек', delta: -10 }, { label: '+10 сек', delta: 10 }, { label: '+1 мин', delta: 60 }, { label: '+5 мин', delta: 300 }, ] const PRESETS = [ { label: '1 мин', seconds: 60 }, { label: '3 мин', seconds: 180 }, { label: '5 мин', seconds: 300 }, { label: '10 мин', seconds: 600 }, { label: '15 мин', seconds: 900 }, { label: '30 мин', seconds: 1800 }, { label: '1 час', seconds: 3600 }, ] const DEFAULT_LABELS = ['Таймер', 'Чайник', 'Паста', 'Яйца', 'Разогрев', 'Стирка', 'Напомни'] export default function TimerModal({ mode, onClose }: TimerModalProps) { return ( {mode && ( e.stopPropagation()} style={{ width: '100%', maxWidth: 520, background: 'var(--surface-1)', border: '1px solid var(--border-subtle)', borderRadius: 28, boxShadow: 'var(--shadow-xl)', padding: 28, display: 'flex', flexDirection: 'column', gap: 22, position: 'relative', }} > {mode.type === 'control' ? ( ) : ( )} )} ) } function ControlView({ timer, onClose }: { timer: Timer; onClose: () => void }) { const [tick, setTick] = useState(0) useEffect(() => { const t = setInterval(() => setTick(x => x + 1), 500) return () => clearInterval(t) }, []) // tick is just to force re-render const now = Date.now() const end = new Date(timer.endsAt).getTime() const start = new Date(timer.startedAt).getTime() const remain = Math.max(0, end - now) const total = Math.max(1, end - start) const progress = Math.max(0, Math.min(1, 1 - remain / total)) const expired = remain <= 0 const imminent = !expired && remain < 10_000 const accent = expired ? '#f87171' : imminent ? '#fb923c' : '#818cf8' const handleAdjust = (delta: number) => { postTimer({ action: 'adjust', id: timer.id, delta_seconds: delta }) } const handleCancel = async () => { await postTimer({ action: 'cancel', id: timer.id }) onClose() } return ( <> {/* Header */}
{expired ? 'Прозвенел' : 'Таймер'}
{timer.label}
{/* Big countdown */}
{formatBig(remain)}
{/* Progress track */}
{/* Adjust grid 3x2 */} {!expired && (
{ADJUST_OPTIONS.map((opt) => { const negative = opt.delta < 0 return ( ) })}
)} {/* Cancel — destructive */} ) } function CreateView({ onClose }: { onClose: () => void }) { const [seconds, setSeconds] = useState(300) // 5 min default const [label, setLabel] = useState('Таймер') const [submitting, setSubmitting] = useState(false) const submit = async () => { if (submitting) return setSubmitting(true) try { await postTimer({ action: 'start', seconds, label: (label.trim() || 'Таймер').slice(0, 80), agent: 'cosmo', }) onClose() } finally { setSubmitting(false) } } return ( <>
Новый таймер
Выбери длительность
{/* Preset grid */}
{PRESETS.map(p => { const active = seconds === p.seconds return ( ) })}
{/* Label selector */}
Название
setLabel(e.target.value)} placeholder="Таймер" maxLength={80} style={{ width: '100%', padding: '14px 16px', borderRadius: 14, background: 'var(--surface-2)', border: '1px solid var(--border-subtle)', color: 'var(--text-primary)', fontSize: 15, fontFamily: 'inherit', outline: 'none', boxSizing: 'border-box' as any, }} />
{DEFAULT_LABELS.map(l => ( ))}
{/* Start button */} ) }