'use client'; import { useState, useEffect, useCallback, useMemo } from 'react'; import React from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { HEALTH_MILESTONES, UsageEntry, UserPreferences } from '@/lib/storage'; import { useTheme } from '@/lib/theme-context'; import { Heart, Wind, HeartPulse, Eye, Activity, TrendingUp, Sparkles, HeartHandshake, CheckCircle2, Cigarette, Leaf } from 'lucide-react'; interface HealthTimelineCardProps { usageData: UsageEntry[]; preferences?: UserPreferences | null; } const iconMap: Record = { Heart, Wind, HeartPulse, Eye, Activity, TrendingUp, Sparkles, HeartHandshake, }; // Simple, direct calculation of minutes since last usage function calculateMinutesFree( substance: 'nicotine' | 'weed', usageData: UsageEntry[], preferences: UserPreferences | null ): number { const now = new Date(); // 1. Check for stored timestamp first (most accurate) const lastUsageTime = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime; if (lastUsageTime) { const lastTime = new Date(lastUsageTime); const diffMs = now.getTime() - lastTime.getTime(); return Math.max(0, diffMs / (1000 * 60)); } // 2. Find last recorded usage from usage data const substanceData = usageData .filter(e => e.substance === substance && e.count > 0) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); if (substanceData.length > 0) { const lastDateStr = substanceData[0].date; const todayStr = now.toISOString().split('T')[0]; // If last usage was today but no timestamp, count from now (0 minutes) if (lastDateStr === todayStr) { return 0; } // For past days, count from end of that day (23:59:59) const lastDate = new Date(lastDateStr); lastDate.setHours(23, 59, 59, 999); const diffMs = now.getTime() - lastDate.getTime(); return Math.max(0, diffMs / (1000 * 60)); } // 3. No usage ever - count from tracking start date if (preferences?.trackingStartDate) { const startDate = new Date(preferences.trackingStartDate); startDate.setHours(0, 0, 0, 0); const diffMs = now.getTime() - startDate.getTime(); return Math.max(0, diffMs / (1000 * 60)); } return 0; } function formatDuration(minutes: number): string { if (minutes < 1) return '< 1 min'; if (minutes < 60) return `${Math.floor(minutes)} min`; if (minutes < 1440) return `${Math.floor(minutes / 60)} hrs`; if (minutes < 10080) return `${Math.floor(minutes / 1440)} days`; if (minutes < 43200) return `${Math.floor(minutes / 10080)} weeks`; if (minutes < 525600) return `${Math.floor(minutes / 43200)} months`; return `${Math.floor(minutes / 525600)} year${minutes >= 1051200 ? 's' : ''}`; } function formatTimeRemaining(currentMinutes: number, targetMinutes: number): string { const remaining = targetMinutes - currentMinutes; if (remaining <= 0) return 'Achieved!'; return `${formatDuration(remaining)} to go`; } interface TimelineColumnProps { substance: 'nicotine' | 'weed'; minutesFree: number; theme: 'light' | 'dark'; } function TimelineColumn({ substance, minutesFree, theme }: TimelineColumnProps) { // Find current milestone let currentMilestoneIndex = -1; for (let i = HEALTH_MILESTONES.length - 1; i >= 0; i--) { if (minutesFree >= HEALTH_MILESTONES[i].timeMinutes) { currentMilestoneIndex = i; break; } } // Find next milestone const nextMilestoneIndex = currentMilestoneIndex + 1; const nextMilestone = nextMilestoneIndex < HEALTH_MILESTONES.length ? HEALTH_MILESTONES[nextMilestoneIndex] : null; // Calculate progress to next milestone let progressToNext = 100; if (nextMilestone) { const prevMinutes = currentMilestoneIndex >= 0 ? HEALTH_MILESTONES[currentMilestoneIndex].timeMinutes : 0; const range = nextMilestone.timeMinutes - prevMinutes; const progress = minutesFree - prevMinutes; progressToNext = Math.min(100, Math.max(0, (progress / range) * 100)); } const substanceLabel = substance === 'nicotine' ? 'Nicotine' : 'Marijuana'; const SubstanceIcon = substance === 'nicotine' ? Cigarette : Leaf; const accentColorClass = substance === 'nicotine' ? 'text-red-500' : 'text-green-500'; const bgAccentClass = substance === 'nicotine' ? 'bg-red-500' : 'bg-green-500'; return (
{/* Header with live timer */}
{substanceLabel} {formatDuration(minutesFree)} free
{/* Progress to next milestone */} {nextMilestone && (
Next Up {formatTimeRemaining(minutesFree, nextMilestone.timeMinutes)}

{nextMilestone.title}

)} {/* Timeline Items */} {HEALTH_MILESTONES.map((milestone, index) => { const isAchieved = minutesFree >= milestone.timeMinutes; const isCurrent = index === currentMilestoneIndex; const Icon = iconMap[milestone.icon] || Heart; return (
{/* Icon */}
{isAchieved ? : }
{/* Content */}

{milestone.title}

{milestone.description}

); })}
); } function HealthTimelineCardComponent({ usageData, preferences, }: HealthTimelineCardProps) { const { theme } = useTheme(); // Calculate last usage timestamps only when data changes const lastUsageTimes = useMemo(() => { const getTimestamp = (substance: 'nicotine' | 'weed') => { // 1. Check for stored timestamp first const stored = substance === 'nicotine' ? preferences?.lastNicotineUsageTime : preferences?.lastWeedUsageTime; if (stored) return new Date(stored).getTime(); // 2. Fallback to usage data const lastEntry = usageData .filter(e => e.substance === substance && e.count > 0) .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0]; if (lastEntry) { const d = new Date(lastEntry.date); d.setHours(23, 59, 59, 999); return d.getTime(); } // 3. Fallback to start date if (preferences?.trackingStartDate) { const d = new Date(preferences.trackingStartDate); d.setHours(0, 0, 0, 0); return d.getTime(); } return null; }; return { nicotine: getTimestamp('nicotine'), weed: getTimestamp('weed') }; }, [usageData, preferences]); // State for live timer values const [nicotineMinutes, setNicotineMinutes] = useState(0); const [weedMinutes, setWeedMinutes] = useState(0); // Update timers using O(1) math from memoized timestamps const updateTimers = useCallback(() => { const now = Date.now(); const msInMin = 1000 * 60; setNicotineMinutes(lastUsageTimes.nicotine ? Math.max(0, (now - lastUsageTimes.nicotine) / msInMin) : 0); setWeedMinutes(lastUsageTimes.weed ? Math.max(0, (now - lastUsageTimes.weed) / msInMin) : 0); }, [lastUsageTimes]); useEffect(() => { updateTimers(); const interval = setInterval(updateTimers, 1000); return () => clearInterval(interval); }, [updateTimers]); const cardBackground = theme === 'light' ? 'linear-gradient(135deg, rgba(236, 253, 245, 0.9) 0%, rgba(209, 250, 229, 0.8) 100%)' : 'linear-gradient(135deg, rgba(20, 184, 166, 0.2) 0%, rgba(6, 182, 212, 0.15) 100%)'; return (
Health Recovery

Track your body's healing process for each substance independently.

); } export const HealthTimelineCard = React.memo(HealthTimelineCardComponent);