import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { motion } from 'framer-motion'; import { ArrowLeft, Edit2, Trash2, TrendingUp, Target, Calendar } from 'lucide-react'; import { Button } from '../components/ui/button'; import { useToast } from '../components/ui/use-toast'; import HabitGrid from '../components/HabitGrid'; import DeleteHabitDialog from '../components/DeleteHabitDialog'; import { getHabits, deleteHabit } from '../lib/datastore'; // Local helper to get habit by id from localStorage function getHabit(id) { const habits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]'); return habits.find(h => h.id === id); } import AnimatedCounter from '../components/AnimatedCounter'; const HabitDetailPage = () => { const { id } = useParams(); const navigate = useNavigate(); const { toast } = useToast(); const [habit, setHabit] = useState(null); const [showDeleteDialog, setShowDeleteDialog] = useState(false); useEffect(() => { // Load and apply saved theme on mount const savedTheme = localStorage.getItem('theme'); if (savedTheme) { document.documentElement.classList.remove('light', 'dark'); document.documentElement.classList.add(savedTheme); } }, []); useEffect(() => { loadHabit(); }, [id]); const loadHabit = () => { const loadedHabit = getHabit(id); if (!loadedHabit) { toast({ title: "Habit not found", description: "This habit doesn't exist or was deleted.", variant: "destructive", }); navigate('/'); return; } setHabit(loadedHabit); }; const handleDelete = () => { // Optimistic local delete const habits = JSON.parse(localStorage.getItem('habitgrid_data') || '[]'); const filtered = habits.filter(h => h.id !== id); localStorage.setItem('habitgrid_data', JSON.stringify(filtered)); deleteHabit(id); // background sync toast({ title: "✅ Habit deleted", description: "Your habit has been removed successfully.", }); navigate('/'); }; if (!habit) { return (
Loading...
); } // Find the oldest completion date let oldestDate = new Date(); if (habit.completions.length > 0) { oldestDate = new Date(habit.completions.reduce((min, d) => d < min ? d : min, habit.completions[0])); } // Calculate streaks of consecutive days function getFullOpacityStreaks(completions) { if (!completions || completions.length === 0) return []; const sorted = [...completions].sort(); let streaks = []; let currentStreak = [sorted[0]]; for (let i = 1; i < sorted.length; i++) { const prev = new Date(sorted[i - 1]); const curr = new Date(sorted[i]); const diff = (curr - prev) / (1000 * 60 * 60 * 24); if (diff === 1) { currentStreak.push(sorted[i]); } else { if (currentStreak.length > 1) streaks.push([...currentStreak]); currentStreak = [sorted[i]]; } } if (currentStreak.length > 1) streaks.push([...currentStreak]); return streaks; } // Bonus: +2% per streak of 3+ full opacity days (capped at +10%) const streaks = getFullOpacityStreaks(habit.completions); const bonus = Math.min(streaks.filter(s => s.length >= 3).length * 2, 10); const completionRate = habit.completions.length > 0 ? (() => { // Overall rate const totalDays = Math.max(1, Math.ceil((Date.now() - oldestDate.getTime()) / (1000 * 60 * 60 * 24))); const overallRate = habit.completions.length / totalDays; // Last 30 days rate const today = new Date(); const lastMonthStart = new Date(today); lastMonthStart.setDate(today.getDate() - 29); const lastMonthDates = []; for (let d = new Date(lastMonthStart); d <= today; d.setDate(d.getDate() + 1)) { lastMonthDates.push(d.toISOString().slice(0, 10)); } const lastMonthCompletions = habit.completions.filter(dateStr => lastMonthDates.includes(dateStr)); const lastMonthRate = lastMonthCompletions.length / 30; // Weighted blend: 70% last month, 30% overall const blendedRate = (lastMonthRate * 0.7) + (overallRate * 0.3); return Math.round(blendedRate * 100 + bonus); })() : 0; return (
{/* Header */}

{habit.name}

Track your daily progress

{/* Stats Cards */}
Current Streak

days in a row

Longest Streak

personal best

Consistency Score!

`${v}%`} />

overall progress

{/* Habit Grid */}
); }; export default HabitDetailPage;