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 (
{/* 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