import { useState, useEffect } from 'react' import { Icon } from '../components/Icons' import SwapWorkoutModal from '../components/SwapWorkoutModal' import '../styles/kinetic-precision.css' const API_URL = '/api' // Uppvärmningsövningar baserat på muskelgrupp const warmupExercises = { general: [ { name: 'Cykel eller roddmaskin', duration: '5 min' }, { name: 'Armcirklar', duration: '30 sek/riktning' }, { name: 'Bensvingar (framåt/bakåt)', duration: '10 per ben' }, { name: 'Bensvingar (sidled)', duration: '10 per ben' }, { name: 'Höftcirklar', duration: '10 per riktning' }, ], specific: { 'Bröst': [ { name: 'Lätta armhävningar', reps: '10-15' }, { name: 'Band pull-aparts', reps: '15-20' }, ], 'Rygg': [ { name: 'Lat stretch', reps: '30 sek/sida' }, { name: 'Lätta rodd-drag', reps: '15-20' }, ], 'Ben': [ { name: 'Bodyweight squats', reps: '15-20' }, { name: 'Utfallssteg', reps: '10/ben' }, ], 'Axlar': [ { name: 'Axelrotationer', reps: '10/riktning' }, { name: 'Band dislocates', reps: '10-15' }, ], 'Armar': [ { name: 'Handledscirklar', reps: '10/riktning' }, { name: 'Lätta bicepscurls', reps: '15-20' }, ], } } // Mappa övningar till muskelgrupper function getMuscleGroups(exercises) { const groups = new Set() exercises.forEach(ex => { if (ex.muscle_group) { groups.add(ex.muscle_group) } }) return Array.from(groups) } function WorkoutPage({ day, week, logs, onLogSet, onDeleteSet, onBack, fetchProgression }) { const [progressions, setProgressions] = useState({}) const [expandedExercise, setExpandedExercise] = useState(null) const [warmupDone, setWarmupDone] = useState(false) const [warmupExpanded, setWarmupExpanded] = useState(true) const [completedWarmups, setCompletedWarmups] = useState(new Set()) const [swapExercise, setSwapExercise] = useState(null) const [alternatives, setAlternatives] = useState([]) const [alternativesLoading, setAlternativesLoading] = useState(false) const [alternativesError, setAlternativesError] = useState('') const [swappedExercises, setSwappedExercises] = useState({}) const [originalExercises, setOriginalExercises] = useState({}) // { exerciseId: originalExercise } const [recentSwaps, setRecentSwaps] = useState({}) // { exerciseId: { undoId, timer } } const [toast, setToast] = useState(null) // { message, type: 'success'|'error' } const defaultRestSeconds = 90 const [restSeconds, setRestSeconds] = useState(defaultRestSeconds) const [restRunning, setRestRunning] = useState(false) useEffect(() => { loadProgressions() }, [day]) useEffect(() => { if (!restRunning) return const timer = setInterval(() => { setRestSeconds(prev => { if (prev <= 1) { setRestRunning(false) return 0 } return prev - 1 }) }, 1000) return () => clearInterval(timer) }, [restRunning]) useEffect(() => { if (!toast) return const timer = setTimeout(() => setToast(null), 3000) return () => clearTimeout(timer) }, [toast]) const loadProgressions = async () => { const progs = {} for (const exercise of day.exercises) { if (exercise.id) { progs[exercise.id] = await fetchProgression(exercise.id) } } setProgressions(progs) } const openAlternatives = async (exercise) => { if (!exercise?.exercise_id) { setAlternativesError('Saknar övningsdata för alternativa val.') setSwapExercise(exercise) return } setSwapExercise(exercise) setAlternatives([]) setAlternativesError('') setAlternativesLoading(true) try { const res = await fetch(`${API_URL}/exercises/${exercise.exercise_id}/alternatives`) if (!res.ok) throw new Error('Failed to fetch alternatives') const data = await res.json() setAlternatives(data) } catch (err) { console.error('Failed to fetch alternatives:', err) setAlternativesError('Kunde inte hämta alternativ.') } finally { setAlternativesLoading(false) } } const handleSwapWorkout = async (alternative) => { if (!swapExercise) return try { setAlternativesLoading(true) // Call API to swap exercise const res = await fetch(`${API_URL}/workouts/${swapExercise.id}/swap`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fromExerciseId: swapExercise.exercise_id, toExerciseId: alternative.exercise_id || alternative.id, workoutDate: day.date }) }) if (!res.ok) throw new Error('Swap failed') const swapData = await res.json() // Update local state setSwappedExercises(prev => ({ ...prev, [swapExercise.id]: alternative })) // Store original exercise for undo setOriginalExercises(prev => ({ ...prev, [swapExercise.id]: swapExercise })) // Show undo button for 30 seconds const undoId = swapData.id || `swap-${swapExercise.id}-${Date.now()}` const timer = setTimeout(() => { setRecentSwaps(prev => { const newSwaps = { ...prev } delete newSwaps[swapExercise.id] return newSwaps }) }, 30000) setRecentSwaps(prev => ({ ...prev, [swapExercise.id]: { undoId, timer } })) setToast({ message: `${swapExercise.name} bytt mot ${alternative.name}`, type: 'success' }) setSwapExercise(null) } catch (err) { console.error('Swap failed:', err) setToast({ message: 'Kunde inte byta övning', type: 'error' }) } finally { setAlternativesLoading(false) } } const undoSwap = async (exerciseId) => { try { const swapInfo = recentSwaps[exerciseId] if (!swapInfo) return // Clear timer clearTimeout(swapInfo.timer) // Call API to undo const res = await fetch(`${API_URL}/workouts/${swapInfo.undoId}/undo`, { method: 'DELETE' }) if (!res.ok) throw new Error('Undo failed') // Update local state setSwappedExercises(prev => { const newSwaps = { ...prev } delete newSwaps[exerciseId] return newSwaps }) setOriginalExercises(prev => { const newOriginals = { ...prev } delete newOriginals[exerciseId] return newOriginals }) setRecentSwaps(prev => { const newSwaps = { ...prev } delete newSwaps[exerciseId] return newSwaps }) setToast({ message: 'Byte ångrat', type: 'success' }) } catch (err) { console.error('Undo failed:', err) setToast({ message: 'Kunde inte ångra byte', type: 'error' }) } } const exercises = day.exercises?.filter(e => e.name) || [] const muscleGroups = getMuscleGroups(exercises) // Beräkna progress const completedExercises = exercises.filter(ex => { const exLogs = logs[ex.id] || [] const completedSets = exLogs.filter(l => l.completed).length return completedSets >= ex.sets }).length const toggleWarmup = (idx) => { const newCompleted = new Set(completedWarmups) if (newCompleted.has(idx)) { newCompleted.delete(idx) } else { newCompleted.add(idx) } setCompletedWarmups(newCompleted) } // Kombinera generell och specifik uppvärmning const generalWarmups = warmupExercises.general const specificWarmups = muscleGroups.flatMap(group => warmupExercises.specific[group] || [] ) const totalWarmups = generalWarmups.length + specificWarmups.length const warmupProgress = completedWarmups.size const formatRestTime = (totalSeconds) => { const minutes = Math.floor(totalSeconds / 60) const seconds = totalSeconds % 60 return `${minutes}:${seconds.toString().padStart(2, '0')}` } const startRest = (seconds = defaultRestSeconds) => { setRestSeconds(seconds) setRestRunning(true) } const toggleRest = () => { setRestRunning(prev => !prev) } const resetRest = () => { setRestRunning(false) setRestSeconds(defaultRestSeconds) } return (