import React, { useState, useEffect, useMemo } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine } from 'recharts'; import { CheckCircle, Circle, Trophy, Flame, Activity, Brain, Droplets, Moon, Dumbbell, Utensils, Ban, Laptop, Settings, Trash2, Plus, X } from 'lucide-react'; // --- Components --- const Card = ({ children, className = "" }) => (
{children}
); const HabitItem = ({ id, label, icon: Icon, checked, onChange, category, isEditing, onDelete }) => { let colorClass = "text-zinc-400"; if (category === 'phys') colorClass = "text-emerald-400"; if (category === 'biz') colorClass = "text-blue-400"; if (category === 'dopa') colorClass = "text-rose-400"; if (category === 'care') colorClass = "text-violet-400"; // Use generic color if checked (or always color in edit mode for clarity) const iconColor = checked || isEditing ? colorClass : "text-zinc-600"; const textColor = checked || isEditing ? "text-zinc-200" : "text-zinc-500"; const bgClass = checked ? 'bg-zinc-800/50 border-zinc-700' : 'bg-zinc-900 border-zinc-800 hover:border-zinc-700'; return (
{label}
{isEditing ? ( ) : ( checked ? ( ) : ( ) )}
); }; const StatCard = ({ label, value, subtext, icon: Icon, color }) => (
{value}
{label}
{subtext &&
{subtext}
}
); // --- Constants --- const DEFAULT_HABITS = [ { id: 'move', label: '10k Steps OR Gym', iconName: 'Dumbbell', category: 'phys' }, { id: 'macros', label: 'Hit Macros (Protein)', iconName: 'Utensils', category: 'phys' }, { id: 'water', label: 'Water (3 Liters)', iconName: 'Droplets', category: 'phys' }, { id: 'sleep', label: 'Sleep (7h+)', iconName: 'Moon', category: 'care' }, { id: 'work', label: 'Deep Work (DS 2h+)', iconName: 'Laptop', category: 'biz' }, { id: 'read', label: 'Read 10 Pages', iconName: 'Brain', category: 'biz' }, { id: 'sugar', label: 'No Sugar', iconName: 'Ban', category: 'dopa' }, { id: 'porn', label: 'No Porn', iconName: 'Flame', category: 'dopa' }, ]; const ICON_MAP = { Dumbbell, Utensils, Droplets, Moon, Laptop, Brain, Ban, Flame, Activity }; const CATEGORIES = [ { id: 'phys', label: 'Physiology', color: 'bg-emerald-500' }, { id: 'biz', label: 'Business', color: 'bg-blue-500' }, { id: 'dopa', label: 'Dopamine', color: 'bg-rose-500' }, { id: 'care', label: 'Self Care', color: 'bg-violet-500' }, ]; const TIME_RANGES = [ { label: '7D', days: 7 }, { label: '30D', days: 30 }, { label: '90D', days: 90 }, { label: '1Y', days: 365 }, ]; // --- Main Application --- export default function MonkModeTracker() { // --- State --- const [history, setHistory] = useState({}); const [habits, setHabits] = useState(DEFAULT_HABITS); const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); const [isEditing, setIsEditing] = useState(false); const [chartRange, setChartRange] = useState(30); // New Habit Form State const [newHabitName, setNewHabitName] = useState(''); const [newHabitCategory, setNewHabitCategory] = useState('phys'); // --- Persistence --- useEffect(() => { // Load History const savedData = localStorage.getItem('monkModeData'); if (savedData) { setHistory(JSON.parse(savedData)); } else { const today = new Date().toISOString().split('T')[0]; setHistory({ [today]: [] }); } // Load Habits const savedHabits = localStorage.getItem('monkModeHabits'); if (savedHabits) { setHabits(JSON.parse(savedHabits)); } }, []); useEffect(() => { if (Object.keys(history).length > 0) { localStorage.setItem('monkModeData', JSON.stringify(history)); } }, [history]); useEffect(() => { localStorage.setItem('monkModeHabits', JSON.stringify(habits)); }, [habits]); // --- Actions --- const toggleHabit = (habitId) => { if (isEditing) return; const currentHabits = history[selectedDate] || []; let newHabits; if (currentHabits.includes(habitId)) { newHabits = currentHabits.filter(h => h !== habitId); } else { newHabits = [...currentHabits, habitId]; } setHistory(prev => ({ ...prev, [selectedDate]: newHabits })); }; const deleteHabit = (habitId) => { if (confirm('Are you sure you want to delete this habit?')) { setHabits(prev => prev.filter(h => h.id !== habitId)); } }; const addHabit = () => { if (!newHabitName.trim()) return; const newId = `custom-${Date.now()}`; const newHabit = { id: newId, label: newHabitName, iconName: 'Activity', // Default icon for custom habits category: newHabitCategory }; setHabits(prev => [...prev, newHabit]); setNewHabitName(''); }; const getDayConsistency = (date) => { const completed = history[date]?.length || 0; if (habits.length === 0) return 0; return Math.round((completed / habits.length) * 100); }; const changeDate = (offset) => { const date = new Date(selectedDate); date.setDate(date.getDate() + offset); const newDateStr = date.toISOString().split('T')[0]; setSelectedDate(newDateStr); if (!history[newDateStr]) { setHistory(prev => ({ ...prev, [newDateStr]: [] })); } }; // --- Analytics --- const chartData = useMemo(() => { const days = []; // Loop based on selected chartRange (7, 30, 90, 365) for (let i = chartRange - 1; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const dateStr = d.toISOString().split('T')[0]; // For longer ranges, we might want cleaner labels, but slicing MM-DD is usually safe days.push({ date: dateStr.slice(5), // MM-DD fullDate: dateStr, score: getDayConsistency(dateStr) || 0 }); } return days; }, [history, habits, chartRange]); const stats = useMemo(() => { const today = new Date().toISOString().split('T')[0]; const score = getDayConsistency(today); let streak = 0; let checkDate = new Date(); while (true) { const dateStr = checkDate.toISOString().split('T')[0]; const dayScore = getDayConsistency(dateStr); if (dateStr !== today && dayScore < 80 && history[dateStr] !== undefined) break; if (history[dateStr] === undefined) break; if (dayScore >= 80) streak++; checkDate.setDate(checkDate.getDate() - 1); } const entries = Object.entries(history); const totalRecordedDays = entries.length; // Note: This calculates win rate based on CURRENT habits length. // Ideally historical win rate should store the % at that time, but this is a close approximation for now. const winningDays = entries.filter(([_, completedIds]) => habits.length > 0 && (completedIds.length / habits.length) >= 0.8 ).length; const winRate = totalRecordedDays > 0 ? Math.round((winningDays / totalRecordedDays) * 100) : 0; return { score, streak, winRate }; }, [history, habits]); return (
{/* Header */}

Monk Mode

Protocol Tracker

{!isEditing && ( <>
Consistency
= 80 ? 'text-emerald-400' : 'text-zinc-600'}`}> {stats.score}%
)}
{/* Edit Mode Warning Banner */} {isEditing && (
Edit Mode Active Add or remove habits below.
)} {/* Date Navigator (Hidden in Edit Mode) */} {!isEditing && (
{new Date(selectedDate).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' })}
)} {/* Stats Row (Hidden in Edit Mode) */} {!isEditing && (
)} {/* Habits List */}
{/* Categories Loop */} {CATEGORIES.map(cat => { const catHabits = habits.filter(h => h.category === cat.id); if (catHabits.length === 0 && !isEditing) return null; return (
{(catHabits.length > 0 || isEditing) && (

{cat.label}

)}
{catHabits.map(habit => ( toggleHabit(habit.id)} isEditing={isEditing} onDelete={deleteHabit} /> ))}
); })} {/* Add New Habit Form */} {isEditing && (

Add New Habit

setNewHabitName(e.target.value)} placeholder="e.g., Cold Shower" className="w-full bg-black border border-zinc-700 rounded-lg p-3 text-white placeholder-zinc-600 focus:outline-none focus:border-emerald-500 transition-colors" />
{CATEGORIES.map(cat => ( ))}
)}
{/* Graph Section */} {!isEditing && (

Performance

{/* Range Selectors */}
{TIME_RANGES.map(range => ( ))}
[`${value}%`, 'Consistency']} />

Green line indicates 80% target

)}
); }