import { useAuth, useCollection, playSound, renderChart } from '@deplixo/sdk'; // DEPLOY_CONFIG: {"cron": [{"name": "weekly_training_stats_summary", "schedule": "0 9 * * 1", "action": "event", "config": {"event_type": "weekly_training_stats_summary"}}]} import { useEffect, useMemo, useRef, useState } from 'react'; import { WorkoutLogger } from './components/WorkoutLogger.jsx'; import { ExerciseHistory } from './components/ExerciseHistory.jsx'; import { RestTimer } from './components/RestTimer.jsx'; function ChartsView({ user }) { const workoutsCollection = useCollection('workouts', { personal: true }); const workouts = workoutsCollection.items || []; const loading = workoutsCollection.loading; const [selectedExercise, setSelectedExercise] = useState(''); const progressionCanvasRef = useRef(null); const weeklyVolumeCanvasRef = useRef(null); const parsedWorkouts = useMemo(() => { return workouts .map((item) => { const data = item?.data || item || {}; const exercises = Array.isArray(data.exercises) ? data.exercises : []; return { ...data, id: item?.id || data.id, createdAt: data.createdAt || item?.createdAt || data.date || item?.date, exercises, }; }) .filter((w) => w.exercises.length > 0) .sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0)); }, [workouts]); const exerciseNames = useMemo(() => { const names = new Set(); parsedWorkouts.forEach((workout) => { workout.exercises.forEach((exercise) => { if (exercise?.name) names.add(exercise.name); }); }); return Array.from(names).sort((a, b) => a.localeCompare(b)); }, [parsedWorkouts]); useEffect(() => { if (!selectedExercise && exerciseNames.length > 0) { setSelectedExercise(exerciseNames[0]); } }, [exerciseNames, selectedExercise]); const progressionData = useMemo(() => { if (!selectedExercise) return { labels: [], data: [] }; const points = []; parsedWorkouts.forEach((workout) => { workout.exercises.forEach((exercise) => { if (exercise?.name !== selectedExercise) return; const sets = Array.isArray(exercise.sets) ? exercise.sets : []; const maxWeight = sets.reduce((max, set) => { const weight = Number(set?.weight || 0); return Number.isFinite(weight) && weight > max ? weight : max; }, 0); points.push({ label: new Date(workout.createdAt).toLocaleDateString(), value: maxWeight, }); }); }); return { labels: points.map((p) => p.label), data: points.map((p) => p.value), }; }, [parsedWorkouts, selectedExercise]); const weeklyVolumeData = useMemo(() => { const weekMap = new Map(); parsedWorkouts.forEach((workout) => { const date = new Date(workout.createdAt); if (Number.isNaN(date.getTime())) return; const start = new Date(date); start.setDate(date.getDate() - date.getDay()); start.setHours(0, 0, 0, 0); const weekKey = start.toISOString(); let volume = 0; workout.exercises.forEach((exercise) => { const sets = Array.isArray(exercise.sets) ? exercise.sets : []; sets.forEach((set) => { const reps = Number(set?.reps || 0); const weight = Number(set?.weight || 0); if (Number.isFinite(reps) && Number.isFinite(weight)) { volume += reps * weight; } }); }); weekMap.set(weekKey, (weekMap.get(weekKey) || 0) + volume); }); const ordered = Array.from(weekMap.entries()) .sort((a, b) => new Date(a[0]) - new Date(b[0])) .slice(-8); return { labels: ordered.map(([week]) => new Date(week).toLocaleDateString(undefined, { month: 'short', day: 'numeric' })), data: ordered.map(([, volume]) => volume), }; }, [parsedWorkouts]); useEffect(() => { if (progressionCanvasRef.current && progressionData.data.length > 0) { renderChart(progressionCanvasRef.current, { type: 'line', data: { labels: progressionData.labels, datasets: [ { label: `${selectedExercise} weight progression`, data: progressionData.data, }, ], }, }); } }, [progressionData, selectedExercise]); useEffect(() => { if (weeklyVolumeCanvasRef.current && weeklyVolumeData.data.length > 0) { renderChart(weeklyVolumeCanvasRef.current, { type: 'bar', data: { labels: weeklyVolumeData.labels, datasets: [ { label: 'Weekly Volume', data: weeklyVolumeData.data, }, ], }, }); } }, [weeklyVolumeData]); if (loading) { return
Loading charts...
; } if (exerciseNames.length === 0) { return (
📈

No chart data yet

Log workouts first, then come back to view weight progression and weekly volume charts.

); } return (

Progression Chart

{exerciseNames.map((name) => ( ))}

Weight Progression Over Time

Weekly Volume

); } function App() { const { user, loading, login, logout } = useAuth(); const [activeTab, setActiveTab] = useState('log'); const [timerVisible, setTimerVisible] = useState(false); const [timerDuration, setTimerDuration] = useState(90); const [timerKey, setTimerKey] = useState(0); const lastCompletedTimerKeyRef = useRef(null); const tabs = [ { id: 'log', label: '🏋️ Log Workout', icon: '💪' }, { id: 'history', label: '📊 History', icon: '📊' }, { id: 'charts', label: '📈 Charts', icon: '📈' }, ]; const startTimer = (seconds) => { setTimerDuration(seconds || 90); setTimerKey((prev) => prev + 1); setTimerVisible(true); lastCompletedTimerKeyRef.current = null; }; const handleCloseTimer = (wasCompleted = false) => { if (wasCompleted && lastCompletedTimerKeyRef.current !== timerKey) { lastCompletedTimerKeyRef.current = timerKey; playSound('@success'); } setTimerVisible(false); }; useEffect(() => { if (!timerVisible) { lastCompletedTimerKeyRef.current = null; } }, [timerVisible]); if (loading) { return
Signing in...
; } if (!user) { return (

🌸 FitBloom

Workout Tracker

🌷

Sign in to view your workouts

Your workout history, personal records, and logged sessions are private to your account. Sign in with Google to continue.

); } return (

🌸 FitBloom

Workout Tracker

{user.avatar ? {user.name} :
{user.name?.[0] || 'U'}
}
{user.name} {user.email}
{activeTab === 'log' && } {activeTab === 'history' && } {activeTab === 'charts' && }
{timerVisible && ( )}
); } export default App; ReactDOM.createRoot(document.getElementById('root')).render();