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 (
{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
)}
);
}