From ce9cd0cce7d2b5b72d204f3d8cc9620ac93433e9 Mon Sep 17 00:00:00 2001 From: Avery Felts Date: Tue, 27 Jan 2026 20:17:33 -0700 Subject: [PATCH] Polish MoodTracker component with premium glassmorphism and UI improvements --- src/components/MoodTracker.tsx | 271 +++++++++++++++++++-------------- 1 file changed, 156 insertions(+), 115 deletions(-) diff --git a/src/components/MoodTracker.tsx b/src/components/MoodTracker.tsx index 2328cd8..e5e3c20 100644 --- a/src/components/MoodTracker.tsx +++ b/src/components/MoodTracker.tsx @@ -3,10 +3,11 @@ import { useState, useEffect, useMemo } from 'react'; import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { Smile, Meh, Frown, TrendingUp, ChevronLeft, ChevronRight, MessageSquare, Quote } from 'lucide-react'; +import { Smile, Meh, Frown, TrendingUp, ChevronLeft, ChevronRight, MessageSquare, Quote, Sparkles } from 'lucide-react'; import { MoodEntry, fetchMoodEntries, saveMoodEntry } from '@/lib/storage'; -import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis, Tooltip, Cell, ReferenceLine } from 'recharts'; -import { format, subDays, startOfWeek, endOfWeek, isWithinInterval, eachDayOfInterval, parseISO } from 'date-fns'; +import { ResponsiveContainer, BarChart, Bar, XAxis, Tooltip, Cell } from 'recharts'; +import { format, subDays, startOfWeek, endOfWeek, eachDayOfInterval } from 'date-fns'; +import { cn } from '@/lib/utils'; export function MoodTracker() { const [entries, setEntries] = useState([]); @@ -22,7 +23,12 @@ export function MoodTracker() { const data = await fetchMoodEntries(); setEntries(data); if (data.length > 0) { - setActiveMood(data[0].mood as any); + // If the most recent entry is today, set it as active + const today = format(new Date(), 'yyyy-MM-dd'); + const lastEntry = data[0]; + if (lastEntry.date === today) { + setActiveMood(lastEntry.mood as any); + } } setIsLoading(false); }; @@ -34,7 +40,6 @@ export function MoodTracker() { setAffirmation(null); setActiveMood(mood); - // Clear previous timeout if it exists if (currentTimeout) { clearTimeout(currentTimeout); setCurrentTimeout(null); @@ -43,10 +48,13 @@ export function MoodTracker() { try { const result = await saveMoodEntry(mood); if (result) { - setEntries(prev => [result.entry, ...prev]); + setEntries(prev => { + // Remove existing entry for today if it exists to avoid duplicates in state + const filtered = prev.filter(e => e.date !== result.entry.date); + return [result.entry, ...filtered]; + }); setAffirmation(result.affirmation); - // Clear affirmation after 8 seconds const timeout = setTimeout(() => { setAffirmation(null); setCurrentTimeout(null); @@ -66,25 +74,23 @@ export function MoodTracker() { return days.map(day => { const dateStr = format(day, 'yyyy-MM-dd'); - const dayEntries = entries.filter(e => e.date === dateStr); + // Find the *latest* entry for this day + const dayEntry = entries.find(e => e.date === dateStr); - // Map mood to numeric values for graphing: bad=1, neutral=2, good=3 + // Map mood to numeric values: bad=1, neutral=2, good=3 let value = 0; - if (dayEntries.length > 0) { - const total = dayEntries.reduce((sum, e) => { - if (e.mood === 'good') return sum + 3; - if (e.mood === 'neutral') return sum + 2; - if (e.mood === 'bad') return sum + 1; - return sum; - }, 0); - value = total / dayEntries.length; + if (dayEntry) { + if (dayEntry.mood === 'good') value = 3; + else if (dayEntry.mood === 'neutral') value = 2; + else if (dayEntry.mood === 'bad') value = 1; } return { name: format(day, 'EEE'), fullDate: dateStr, - value: value === 0 ? null : Number(value.toFixed(1)), - count: dayEntries.length + value: value === 0 ? 0.2 : value, // 0.2 provides a small placeholder bar + isPlaceholder: value === 0, + mood: dayEntry?.mood }; }); }, [entries, weekOffset]); @@ -97,145 +103,180 @@ export function MoodTracker() { return `${format(start, 'MMM d')} - ${format(end, 'MMM d')}`; }, [weekOffset]); - // Dynamic styles based on active mood - const getMoodStyles = () => { + const getGradient = () => { switch (activeMood) { case 'good': - return 'from-emerald-600/20 via-teal-500/10 to-indigo-500/10 border-emerald-500/30 shadow-emerald-500/10'; + return 'from-emerald-500/10 via-teal-500/5 to-emerald-500/10 border-emerald-500/20 shadow-emerald-500/10'; case 'neutral': - return 'from-amber-600/20 via-orange-500/10 to-purple-500/10 border-amber-500/30 shadow-amber-500/10'; + return 'from-amber-500/10 via-orange-500/5 to-amber-500/10 border-amber-500/20 shadow-amber-500/10'; case 'bad': - return 'from-rose-600/20 via-pink-500/10 to-blue-500/10 border-rose-500/30 shadow-rose-500/10'; + return 'from-rose-500/10 via-red-500/5 to-rose-500/10 border-rose-500/20 shadow-rose-500/10'; default: - return 'from-indigo-600/20 via-purple-500/10 to-pink-500/10 border-white/10 shadow-indigo-500/10'; + return 'from-violet-500/10 via-indigo-500/5 to-violet-500/10 border-white/10 shadow-indigo-500/5'; } }; return ( - - + +
- - - How do you feel? + +
+ +
+ How are you feeling?
-
+ +
- {weekLabel} + + {weekLabel} +
- - {/* Mood Buttons */} -
- + + {/* Mood Selection */} +
+ {[ + { id: 'good', icon: Smile, label: 'Good', color: 'emerald' }, + { id: 'neutral', icon: Meh, label: 'Okay', color: 'amber' }, + { id: 'bad', icon: Frown, label: 'Bad', color: 'rose' } + ].map((item) => { + const isSelected = activeMood === item.id; + const Icon = item.icon; - - - + return ( + + ); + })}
- {/* Affirmation Message */} - {affirmation && ( -
- -

{affirmation}

+ {/* Dynamic Affirmation */} +
+
+
+ +
+

+ {affirmation} +

- )} +
- {/* Week Graph */} -
-
+ {/* Mini Graph */} +
+
- Mood Trend + Mood Tracking
-
+ +
+ {/* Grid lines background effect */} +
+ - + + { + if (active && payload && payload.length) { + const data = payload[0].payload; + if (data.isPlaceholder) return null; + + return ( +
+

{data.fullDate}

+

+ {data.mood} +

+
+ ); + } + return null; + }} + /> - - { - if (value === undefined) return ['', '']; - if (value >= 2.5) return ['Good', 'Mood']; - if (value >= 1.5) return ['Neutral', 'Mood']; - if (value > 0) return ['Bad', 'Mood']; - return ['No Record', 'Mood']; - }} - /> - + {weeklyData.map((entry, index) => { - let color = 'rgba(255,255,255,0.1)'; - if (entry.value) { - if (entry.value >= 2.5) color = '#10b981'; // emerald-500 - else if (entry.value >= 1.5) color = '#f59e0b'; // amber-500 - else color = '#f43f5e'; // rose-500 + let color = 'rgba(255,255,255,0.05)'; // Placeholder color + + if (!entry.isPlaceholder) { + if (entry.mood === 'good') color = '#10b981'; // emerald-500 + else if (entry.mood === 'neutral') color = '#f59e0b'; // amber-500 + else if (entry.mood === 'bad') color = '#f43f5e'; // rose-500 } - return ; + + return ( + + ); })}