'use client'; import { useTimerStore } from '@/lib/store'; import { getRemainingMs } from '@/lib/timer-engine'; import type { Timer } from '@/lib/timer-engine'; import { getUrgencyConfig } from '@/lib/urgency'; import { formatDuration, formatTime, formatDurationCompact } from '@/lib/format'; import { formatMinutesBefore } from '@/lib/cascade'; import { Clock, Pause, Play, X, AlarmClock, Timer as TimerIcon, Coffee, Bell, BellOff, Repeat, Link2, Tag, } from 'lucide-react'; import { getTimeReferenceMs } from '@/lib/time-blindness'; import { getSuggestedPrepTime, shouldShowPrepWarning, formatPrepWarning } from '@/lib/prep-time'; interface TimerCardProps { timer: Timer; } export function TimerCard({ timer }: TimerCardProps) { const now = useTimerStore((s) => s.now); const { pause, resume, dismiss, snooze, advancePom } = useTimerStore(); const urgencyConfig = getUrgencyConfig(timer.urgency); const remaining = getRemainingMs(timer, now); const typeIcon = { alarm: , countdown: , pomodoro: , event: , }[timer.type]; const stateLabel = { idle: 'Idle', active: 'Active', warning: 'Warning', firing: 'FIRING!', snoozed: 'Snoozed', dismissed: 'Dismissed', completed: 'Done', paused: 'Paused', }[timer.state]; const isFiring = timer.state === 'firing'; const isPaused = timer.state === 'paused'; const isActive = ['active', 'warning', 'snoozed'].includes(timer.state); const isDone = ['dismissed', 'completed'].includes(timer.state); // Pomodoro label const pomLabel = timer.pomodoroState ? timer.pomodoroState.isBreak || timer.pomodoroState.isLongBreak ? `Break` : `Round ${timer.pomodoroState.currentRound}/${timer.pomodoroConfig?.rounds ?? 4}` : null; // Next warning const nextWarning = timer.warnings.find((w) => !w.fired); return (
{/* Header */}
{typeIcon} {timer.type.charAt(0).toUpperCase() + timer.type.slice(1)} {pomLabel && ` · ${pomLabel}`} {urgencyConfig.label} {timer.category && ( {timer.category} )} {timer.recurringTimerId && ( )} {timer.linkedTimerId && ( )}
{stateLabel}
{/* Label */}

{timer.label}

{/* Countdown / Time */} {!isDone && timer.type === 'event' && (
{/* Large days display for event countdowns */} {(() => { const days = Math.floor(remaining / 86_400_000); const hours = Math.floor((remaining % 86_400_000) / 3_600_000); const totalDays = timer.duration ? Math.ceil(timer.duration / 86_400_000) : days; const progress = totalDays > 0 ? Math.max(0, Math.min(1, 1 - days / totalDays)) : 0; return ( <>
{days} day{days !== 1 ? 's' : ''} {hours}h remaining
{/* Milestone progress bar */}
0.9 ? 'var(--cm-danger)' : progress > 0.7 ? 'var(--cm-warning)' : 'var(--cm-accent)', }} />
Created {new Date(timer.targetTime).toLocaleDateString()}
); })()}
)} {!isDone && timer.type !== 'event' && (
{formatDuration(remaining)} fires at {formatTime(timer.targetTime)}
{/* Time blindness aid */} {remaining > 60_000 && (

{getTimeReferenceMs(remaining)}

)}
)} {/* Cascade warnings progress */} {timer.warnings.length > 0 && !isDone && (
{timer.warnings.map((w) => (
))}
)} {/* Next warning */} {nextWarning && !isDone && !isFiring && (

Next warning: {formatMinutesBefore(nextWarning.minutesBefore)} before

)} {/* Custom message or prep time warning */} {!isDone && !isFiring && timer.customMessage && (

💬 {timer.customMessage}

)} {!isDone && !isFiring && !timer.customMessage && (() => { const prepConfig = getSuggestedPrepTime(timer.label, timer.category); const minutesUntil = Math.round(remaining / 60_000); if (shouldShowPrepWarning(timer.targetTime, prepConfig, now)) { return (

⏱ {formatPrepWarning(prepConfig, minutesUntil)}

); } return null; })()} {/* Snooze info */} {timer.snoozeCount > 0 && (

Snoozed {timer.snoozeCount}x

)} {/* Actions */}
{isActive && timer.type !== 'alarm' && ( )} {isPaused && ( )} {isFiring && ( <> {timer.type === 'pomodoro' && ( )} )} {(isActive || isPaused || isFiring) && ( )}
); }