import { useState, useRef, useEffect, useCallback } from 'react'; import { X, Dumbbell, Pill, FileText, Plus, Pencil, Trash2, CheckCircle2, Circle } from 'lucide-react'; import { toast } from 'sonner'; import type { DayDetail, CalendarEvent as CalEvent } from '../../types/calendar'; import { upsertNote, deleteEvent } from '../../api/calendar'; import CalendarEventForm from './CalendarEventForm'; const MOOD_OPTIONS = [ { value: 'great', emoji: '๐Ÿ˜„', label: 'Great' }, { value: 'good', emoji: '๐Ÿ™‚', label: 'Good' }, { value: 'okay', emoji: '๐Ÿ˜', label: 'Okay' }, { value: 'bad', emoji: '๐Ÿ˜•', label: 'Bad' }, { value: 'awful', emoji: '๐Ÿ˜ž', label: 'Awful' }, ]; function formatDate(dateStr: string): string { const [year, month, day] = dateStr.split('-').map(Number); return new Date(year, month - 1, day).toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', }); } function colorClass(color?: string): string { const map: Record = { blue: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300', green: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-300', red: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-300', purple: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-300', orange: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-300', pink: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-300', }; return color && map[color] ? map[color] : 'bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300'; } // โ”€โ”€ Workout Tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function WorkoutTab({ detail, onRefresh }: { detail: DayDetail; onRefresh: () => void }) { const [showForm, setShowForm] = useState(false); const [editEvent, setEditEvent] = useState(null); const workoutEvents = detail.events.filter(e => e.event_type === 'workout'); const handleDelete = async (id: number) => { try { await deleteEvent(id); toast.success('Event deleted'); onRefresh(); } catch { toast.error('Failed to delete event'); } }; if (showForm || editEvent) { return (

{editEvent ? 'Edit Event' : 'Add Workout Event'}

{ setShowForm(false); setEditEvent(null); onRefresh(); }} onCancel={() => { setShowForm(false); setEditEvent(null); }} />
); } return (
{/* Kettlebell sessions */} {detail.kettlebell_sessions.length > 0 && (

Kettlebell Sessions

{detail.kettlebell_sessions.map(kb => (

{kb.title}

{kb.focus} ยท {kb.total_duration_min} min ยท {kb.difficulty}

{kb.status}
))}
)} {/* Workout events */} {workoutEvents.length > 0 && (

Workout Events

{workoutEvents.map(ev => (
{ev.is_completed ? : }

{ev.title}

{ev.description && (

{ev.description}

)} {ev.start_time && (

{ev.start_time}

)}
))}
)} {detail.kettlebell_sessions.length === 0 && workoutEvents.length === 0 && (

No workouts logged for this day.

)}
); } // โ”€โ”€ Supplements Tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function SupplementsTab({ detail }: { detail: DayDetail }) { const taken = detail.supplements.filter(s => s.taken_today); const notTaken = detail.supplements.filter(s => !s.taken_today); const compliance = detail.supplements.length > 0 ? Math.round((taken.length / detail.supplements.length) * 100) : null; return (
{compliance !== null && (
Compliance {taken.length}/{detail.supplements.length}
= 80 ? 'bg-green-500' : compliance >= 50 ? 'bg-yellow-500' : 'bg-red-500'}`} style={{ width: `${compliance}%` }} />
{compliance}%
)} {detail.supplements.length === 0 && (

No active supplements.

)} {taken.length > 0 && (

Taken

{taken.map(s => (
{s.name} {s.dosage} {s.unit}
))}
)} {notTaken.length > 0 && (

Not Taken

{notTaken.map(s => (
{s.name} {s.dosage} {s.unit}
))}
)}
); } // โ”€โ”€ Notes Tab โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ function NotesTab({ detail, onRefresh }: { detail: DayDetail; onRefresh: () => void }) { const [content, setContent] = useState(detail.note?.content ?? ''); const [mood, setMood] = useState(detail.note?.mood ?? ''); const [energyLevel, setEnergyLevel] = useState(detail.note?.energy_level ?? 5); const [saveStatus, setSaveStatus] = useState<'idle' | 'saving' | 'saved'>('idle'); const debounceRef = useRef | null>(null); const save = useCallback(async (c: string, m: string, e: number) => { setSaveStatus('saving'); try { await upsertNote(detail.date, { content: c, mood: m || undefined, energy_level: e, }); setSaveStatus('saved'); onRefresh(); } catch { setSaveStatus('idle'); toast.error('Failed to save note'); } }, [detail.date, onRefresh]); const scheduleAutosave = (c: string, m: string, e: number) => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => save(c, m, e), 1500); }; useEffect(() => { return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, []); const handleContentChange = (val: string) => { if (val.length > 10000) return; setContent(val); setSaveStatus('idle'); scheduleAutosave(val, mood, energyLevel); }; const handleMoodChange = (val: string) => { const newMood = mood === val ? '' : val; setMood(newMood); setSaveStatus('idle'); scheduleAutosave(content, newMood, energyLevel); }; const handleEnergyChange = (val: number) => { setEnergyLevel(val); setSaveStatus('idle'); scheduleAutosave(content, mood, val); }; return (
{saveStatus === 'saving' && 'Saving...'} {saveStatus === 'saved' && Saved} {saveStatus === 'idle' && `${content.length}/10000`}