import { useState, useEffect } from 'react' import { Icon } from '../components/Icons' import AlternativeModal from '../components/AlternativeModal' 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 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]) 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 handleSelectAlternative = (alternative) => { if (!swapExercise) return setSwappedExercises(prev => ({ ...prev, [swapExercise.id]: alternative })) setSwapExercise(null) } 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 (

{day.name}

Vecka {week} • Dag {day.day_number}
{completedExercises}/{exercises.length}
{/* Vila */}
Vilotimer
{formatRestTime(restSeconds)}
{/* Progress Bar */}
{/* Uppvärmningssektion */}
setWarmupExpanded(!warmupExpanded)} >

Uppvärmning

{warmupProgress}/{totalWarmups}
{warmupExpanded && (
{/* Generell uppvärmning */}

Generell uppvärmning (5-10 min)

{generalWarmups.map((warmup, idx) => (
toggleWarmup(idx)} > {completedWarmups.has(idx) ? : ''} {warmup.name} {warmup.duration || warmup.reps}
))}
{/* Specifik uppvärmning */} {specificWarmups.length > 0 && (

Specifik för {muscleGroups.join(', ')}

{specificWarmups.map((warmup, idx) => { const globalIdx = generalWarmups.length + idx return (
toggleWarmup(globalIdx)} > {completedWarmups.has(globalIdx) ? : ''} {warmup.name} {warmup.reps}
) })}
)} {/* Lätt set av första övningen */} {exercises[0] && (

Förberedande set

{ const newCompleted = new Set(completedWarmups) if (newCompleted.has('prep')) { newCompleted.delete('prep') } else { newCompleted.add('prep') } setCompletedWarmups(newCompleted) }} > {completedWarmups.has('prep') ? : ''} Lätta set {exercises[0].name} 2x10 @ 50%
)}
)}
{/* Övningslista */}

Övningar

{exercises.map((exercise, idx) => { const swapped = swappedExercises[exercise.id] const displayExercise = swapped ? { ...exercise, name: swapped.name, muscle_group: swapped.muscle_group, description: swapped.description } : exercise return ( setExpandedExercise( expandedExercise === exercise.id ? null : exercise.id )} onLogSet={onLogSet} onDeleteSet={onDeleteSet} onStartRest={startRest} onSwap={() => openAlternatives(exercise)} /> ) })}
{/* Avsluta pass */}
setSwapExercise(null)} />
) } function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSet, onDeleteSet, onSwap, isSwapped, onStartRest }) { const [setList, setSetList] = useState([]) const [showAddModal, setShowAddModal] = useState(false) const weightStep = 2.5 const repsStep = 1 useEffect(() => { const initial = [] for (let i = 1; i <= exercise.sets; i++) { const existingLog = logs.find(l => l.set_number === i) initial.push({ weight: existingLog?.weight?.toString() || progression?.suggestedWeight?.toString() || '', reps: existingLog?.reps?.toString() || '', completed: existingLog?.completed || false }) } setSetList(initial) }, [exercise, logs, progression]) const handleInputChange = (idx, field, value) => { setSetList(prev => prev.map((s, i) => i === idx ? { ...s, [field]: value } : s)) } const parseNumber = (value) => { const parsed = parseFloat(value) return Number.isFinite(parsed) ? parsed : 0 } const formatWeight = (value) => { const fixed = Number.isInteger(value) ? String(value) : value.toFixed(1) return fixed.replace(/\.0$/, '') } const handleAdjust = (idx, field, delta, min = 0) => { const current = parseNumber(setList[idx]?.[field]) const next = Math.max(min, current + delta) if (field === 'weight') { handleInputChange(idx, field, formatWeight(next)) } else { handleInputChange(idx, field, String(Math.round(next))) } } const handleComplete = (idx) => { const input = setList[idx] const newCompleted = !input.completed setSetList(prev => prev.map((s, i) => i === idx ? { ...s, completed: newCompleted } : s)) onLogSet(exercise.id, idx + 1, input.weight, input.reps, newCompleted) if (newCompleted) { onStartRest?.() } } const handleAddNormal = () => { const last = setList[setList.length - 1] || { weight: '', reps: '' } setSetList(prev => [...prev, { weight: last.weight, reps: last.reps, completed: false }]) setShowAddModal(false) } const handleAddDropset = () => { const last = setList[setList.length - 1] || { weight: '0', reps: '10' } const baseWeight = parseFloat(last.weight) || 0 const drop1 = Math.round((baseWeight * 0.80) / 2.5) * 2.5 const drop2 = Math.round((baseWeight * 0.60) / 2.5) * 2.5 const newSets = [ { weight: last.weight, reps: '10', completed: false }, { weight: drop1.toString(), reps: '10', completed: false }, { weight: drop2.toString(), reps: '10', completed: false }, ] setSetList(prev => [...prev, ...newSets]) setShowAddModal(false) } const handleDeleteSet = (idx) => { if (setList.length <= 1) return setSetList(prev => prev.filter((_, i) => i !== idx)) if (onDeleteSet) onDeleteSet(exercise.id, idx + 1) } const completedSets = setList.filter(s => s.completed).length return (
0 ? 'all-done' : ''}`}>

{exercise.name}

{exercise.muscle_group} {isSwapped && Alternativ}
{exercise.sets}×{exercise.reps_min}-{exercise.reps_max} {completedSets}/{setList.length}
{expanded && (
{progression && (
{progression.reason} {progression.suggestedWeight && ( {progression.suggestedWeight} kg )}
)}
{setList.map((input, idx) => (
Set {idx + 1}
Vikt
{input.weight === '' ? '0' : input.weight} kg
Reps
{input.reps === '' ? '0' : input.reps}
))}
{showAddModal && (
setShowAddModal(false)}>
e.stopPropagation()}>

Välj settyp

)}
)}
) } export default WorkoutPage