Files
gravl/frontend/src/pages/WorkoutPage.jsx
T
clawd 1f2a892391 feat(frontend): Kinetic Precision design system — new lime theme, glassmorphism, redesigned pages
- New design system: Stitch (kinetic-precision.css) with lime (#cafd00) accent
- New Google Fonts: Lexend, Plus Jakarta Sans, Space Grotesk
- New page: BenchmarksPage with strength/endurance/body tracking
- Redesigned: Dashboard, ProgressPage, WorkoutPage, LoginPage + LoginPage.css
- Add shared glassmorphism nav, kinetic buttons, intensity indicators
- Build: 265KB JS / 88KB CSS / 2.54s (clean)
2026-04-27 08:49:23 +02:00

858 lines
30 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 (
<div className="workout-page">
<header className="page-header">
<button className="back-btn" onClick={onBack}>
<Icon name="arrowLeft" size={16} /> Tillbaka
</button>
<div className="header-center">
<h1>{day.name}</h1>
<span className="header-subtitle">Vecka {week} Dag {day.day_number}</span>
</div>
<div className="header-progress">
<span className="progress-text">{completedExercises}/{exercises.length}</span>
</div>
</header>
<main className="page-main workout-main">
{/* Vila */}
<section className="rest-timer-card">
<div className="rest-timer-header">
<div className="rest-timer-label">Vilotimer</div>
<div className={`rest-timer-time ${restRunning ? 'running' : ''}`}>
{formatRestTime(restSeconds)}
</div>
</div>
<div className="rest-timer-actions">
<button className="rest-timer-btn primary" onClick={toggleRest}>
{restRunning ? 'Pausa' : 'Starta vila'}
</button>
<button className="rest-timer-btn secondary" onClick={resetRest}>
Återställ
</button>
</div>
<div className="rest-timer-presets">
<button className="rest-timer-chip" onClick={() => startRest(60)}>1:00</button>
<button className="rest-timer-chip" onClick={() => startRest(90)}>1:30</button>
<button className="rest-timer-chip" onClick={() => startRest(120)}>2:00</button>
</div>
</section>
{/* Progress Bar */}
<div className="workout-progress-bar">
<div
className="workout-progress-fill"
style={{ width: `${(completedExercises / exercises.length) * 100}%` }}
/>
</div>
{/* Uppvärmningssektion */}
<section className={`warmup-section ${warmupDone ? 'completed' : ''}`}>
<div
className="warmup-header"
onClick={() => setWarmupExpanded(!warmupExpanded)}
>
<div className="warmup-title">
<span className="warmup-icon"><Icon name="fire" size={20} /></span>
<h2>Uppvärmning</h2>
<span className="warmup-progress">{warmupProgress}/{totalWarmups}</span>
</div>
<span className={`expand-icon ${warmupExpanded ? 'expanded' : ''}`}>
<Icon name="chevronDown" size={16} />
</span>
</div>
{warmupExpanded && (
<div className="warmup-content">
{/* Generell uppvärmning */}
<div className="warmup-category">
<h3>Generell uppvärmning (5-10 min)</h3>
<div className="warmup-list">
{generalWarmups.map((warmup, idx) => (
<div
key={idx}
className={`warmup-item ${completedWarmups.has(idx) ? 'done' : ''}`}
onClick={() => toggleWarmup(idx)}
>
<span className="warmup-check">
{completedWarmups.has(idx) ? <Icon name="check" size={14} /> : ''}
</span>
<span className="warmup-name">{warmup.name}</span>
<span className="warmup-duration">{warmup.duration || warmup.reps}</span>
</div>
))}
</div>
</div>
{/* Specifik uppvärmning */}
{specificWarmups.length > 0 && (
<div className="warmup-category">
<h3>Specifik för {muscleGroups.join(', ')}</h3>
<div className="warmup-list">
{specificWarmups.map((warmup, idx) => {
const globalIdx = generalWarmups.length + idx
return (
<div
key={globalIdx}
className={`warmup-item ${completedWarmups.has(globalIdx) ? 'done' : ''}`}
onClick={() => toggleWarmup(globalIdx)}
>
<span className="warmup-check">
{completedWarmups.has(globalIdx) ? <Icon name="check" size={14} /> : ''}
</span>
<span className="warmup-name">{warmup.name}</span>
<span className="warmup-duration">{warmup.reps}</span>
</div>
)
})}
</div>
</div>
)}
{/* Lätt set av första övningen */}
{exercises[0] && (
<div className="warmup-category">
<h3>Förberedande set</h3>
<div className="warmup-list">
<div
className={`warmup-item ${completedWarmups.has('prep') ? 'done' : ''}`}
onClick={() => {
const newCompleted = new Set(completedWarmups)
if (newCompleted.has('prep')) {
newCompleted.delete('prep')
} else {
newCompleted.add('prep')
}
setCompletedWarmups(newCompleted)
}}
>
<span className="warmup-check">
{completedWarmups.has('prep') ? <Icon name="check" size={14} /> : ''}
</span>
<span className="warmup-name">Lätta set {exercises[0].name}</span>
<span className="warmup-duration">2x10 @ 50%</span>
</div>
</div>
</div>
)}
<button
className={`warmup-done-btn ${warmupDone ? 'completed' : ''}`}
onClick={() => setWarmupDone(!warmupDone)}
>
{warmupDone ? (
<><Icon name="check" size={18} /> Uppvärmning klar</>
) : (
'Markera uppvärmning som klar'
)}
</button>
</div>
)}
</section>
{/* Övningslista */}
<section className="exercises-section">
<h2>Övningar</h2>
{exercises.map((exercise, idx) => {
const swapped = swappedExercises[exercise.id]
const original = originalExercises[exercise.id]
const displayExercise = swapped
? { ...exercise, name: swapped.name, muscle_group: swapped.muscle_group, description: swapped.description }
: exercise
return (
<ExerciseCard
key={exercise.id || idx}
exercise={displayExercise}
originalExercise={original}
isSwapped={Boolean(swapped)}
logs={logs[exercise.id] || []}
progression={progressions[exercise.id]}
expanded={expandedExercise === exercise.id}
onToggle={() => setExpandedExercise(
expandedExercise === exercise.id ? null : exercise.id
)}
onLogSet={onLogSet}
onDeleteSet={onDeleteSet}
onStartRest={startRest}
onSwap={() => openAlternatives(exercise)}
onUndo={() => undoSwap(exercise.id)}
canUndo={Boolean(recentSwaps[exercise.id])}
exerciseIndex={idx + 1}
totalExercises={exercises.length}
/>
)
})}
</section>
{/* Avsluta pass */}
<button
className={`finish-workout-btn ${completedExercises === exercises.length ? 'ready' : ''}`}
onClick={onBack}
>
{completedExercises === exercises.length
? 'Avsluta pass'
: `Avsluta pass (${completedExercises}/${exercises.length} klara)`}
</button>
</main>
<SwapWorkoutModal
exercise={swapExercise}
alternatives={alternatives}
loading={alternativesLoading}
error={alternativesError}
onSwap={handleSwapWorkout}
onClose={() => setSwapExercise(null)}
/>
{/* Toast Notification */}
{toast && (
<div className={`toast-notification toast-${toast.type}`}>
{toast.message}
</div>
)}
</div>
)
}
function ExerciseCard({ exercise, logs, progression, expanded, onToggle, onLogSet, onDeleteSet, onSwap, isSwapped, onStartRest, originalExercise, onUndo, canUndo, exerciseIndex, totalExercises }) {
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
// Compute PR: current set weight exceeds progression last weight
const isPR = (input, idx) => {
const lastWeight = progression?.lastWeight
if (!lastWeight) return false
const w = parseFloat(input.weight)
return !isNaN(w) && w > lastWeight
}
return (
<div className={`exercise-card ${expanded ? 'expanded' : ''} ${completedSets === setList.length && setList.length > 0 ? 'all-done' : ''}`}>
{/* EXERCISE FOCUS HEADER */}
<div className="exercise-header" onClick={onToggle} style={{ paddingBottom: expanded ? '0.5rem' : undefined }}>
<div style={{ flex: 1 }}>
{/* Progress indicator */}
{exerciseIndex != null && (
<span style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.65rem',
color: '#767575',
letterSpacing: '0.06em',
textTransform: 'uppercase',
display: 'block',
marginBottom: '0.25rem',
}}>Övning {exerciseIndex} av {totalExercises}</span>
)}
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<h3 style={{
fontFamily: 'Lexend, sans-serif',
fontWeight: 700,
fontSize: '1.1rem',
color: '#ffffff',
margin: 0,
}}>{exercise.name}</h3>
{isSwapped && originalExercise && (
<span className="swap-badge" style={{ fontSize: '0.6rem' }}>Bytt</span>
)}
</div>
{exercise.muscle_group && (
<span style={{
display: 'inline-block',
marginTop: '0.25rem',
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.7rem',
color: '#767575',
letterSpacing: '0.03em',
}}>{exercise.muscle_group}</span>
)}
</div>
<div className="exercise-actions">
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: '0.25rem' }}>
<span style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.75rem',
color: '#adaaaa',
letterSpacing: '0.03em',
}}>{exercise.sets}×{exercise.reps_min}-{exercise.reps_max}</span>
<span className={`progress-badge ${completedSets === setList.length ? 'complete' : ''}`}>
{completedSets}/{setList.length}
</span>
</div>
<div className="exercise-buttons">
<button
className="swap-btn"
onClick={(event) => {
event.stopPropagation()
onSwap?.()
}}
aria-label="Byt övning"
title="Byt övning"
>
<Icon name="swap" size={16} />
</button>
{canUndo && (
<button
className="undo-btn"
onClick={(event) => {
event.stopPropagation()
onUndo?.()
}}
aria-label="Ångra byte"
title="Ångra byte"
>
<Icon name="undo" size={16} />
</button>
)}
</div>
</div>
</div>
{expanded && (
<div className="exercise-body">
{/* Progression hint */}
{progression && (
<div className="progression-hint" style={{ marginBottom: '0.75rem' }}>
{progression.reason}
{progression.suggestedWeight && (
<strong> {progression.suggestedWeight} kg</strong>
)}
</div>
)}
{/* Target line */}
{(exercise.reps_min || exercise.reps_max) && (
<div style={{
background: '#131313',
borderRadius: '6px',
padding: '0.5rem 0.75rem',
marginBottom: '0.75rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
}}>
<span style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.7rem',
color: '#767575',
letterSpacing: '0.04em',
textTransform: 'uppercase',
}}>Mål</span>
<span style={{
fontFamily: 'Lexend, sans-serif',
fontWeight: 700,
fontSize: '0.875rem',
color: '#adaaaa',
}}>
{exercise.sets} set · {exercise.reps_min}{exercise.reps_max && exercise.reps_max !== exercise.reps_min ? `${exercise.reps_max}` : ''} reps
</span>
</div>
)}
<div className="sets-list">
{setList.map((input, idx) => {
const setIsPR = isPR(input, idx)
return (
<div key={idx} className={`set-row ${input.completed ? 'completed' : ''}`}>
<div className="set-row-top">
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<span className="set-number" style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.75rem',
color: '#767575',
letterSpacing: '0.04em',
textTransform: 'uppercase',
}}>Set {idx + 1}</span>
{setIsPR && (
<span style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.6rem',
fontWeight: 700,
color: '#516700',
background: '#cafd00',
padding: '0.1rem 0.35rem',
borderRadius: '4px',
letterSpacing: '0.04em',
}}>PR</span>
)}
</div>
<button
className={`delete-set-btn ${setList.length <= 1 ? 'disabled' : ''}`}
onClick={() => handleDeleteSet(idx)}
disabled={setList.length <= 1}
aria-label={`Ta bort set ${idx + 1}`}
>
<Icon name="trash" size={16} />
</button>
</div>
<div className="set-controls">
<div className="set-metric">
<span className="metric-label">Vikt</span>
<div className="metric-controls">
<button
type="button"
className="metric-btn"
onClick={() => handleAdjust(idx, 'weight', -weightStep)}
aria-label="Minska vikt"
>
</button>
<div className="metric-value">
<span className="metric-number" style={{
fontFamily: 'Lexend, sans-serif',
color: '#cafd00',
fontSize: '1.35rem',
fontWeight: 700,
}}>{input.weight === '' ? '0' : input.weight}</span>
<span className="metric-suffix">kg</span>
</div>
<button
type="button"
className="metric-btn"
onClick={() => handleAdjust(idx, 'weight', weightStep)}
aria-label="Öka vikt"
>
+
</button>
</div>
</div>
<div className="set-metric">
<span className="metric-label">Reps</span>
<div className="metric-controls">
<button
type="button"
className="metric-btn"
onClick={() => handleAdjust(idx, 'reps', -repsStep)}
aria-label="Minska reps"
>
</button>
<div className="metric-value">
<span className="metric-number" style={{
fontFamily: 'Lexend, sans-serif',
fontSize: '1.35rem',
fontWeight: 700,
}}>{input.reps === '' ? '0' : input.reps}</span>
</div>
<button
type="button"
className="metric-btn"
onClick={() => handleAdjust(idx, 'reps', repsStep)}
aria-label="Öka reps"
>
+
</button>
</div>
</div>
</div>
{/* Previous session reference */}
{progression?.lastWeight && progression?.lastReps && (
<div style={{
fontFamily: 'Space Grotesk, monospace',
fontSize: '0.7rem',
color: '#767575',
letterSpacing: '0.03em',
marginTop: '0.25rem',
marginBottom: '0.25rem',
}}>
Förra träningen: {progression.lastWeight}kg×{progression.lastReps}
</div>
)}
<button
className={`klart-btn ${input.completed ? 'done' : ''}`}
onClick={() => handleComplete(idx)}
>
{input.completed ? <Icon name="check" size={18} /> : null}
KLART
</button>
</div>
)
})}
</div>
<button
className="add-set-btn"
onClick={() => setShowAddModal(true)}
>
+ Lägg till set
</button>
{showAddModal && (
<div className="set-type-modal-overlay" onClick={() => setShowAddModal(false)}>
<div className="set-type-modal" onClick={e => e.stopPropagation()}>
<h3>Välj settyp</h3>
<button className="set-type-option" onClick={handleAddNormal}>
<strong>Vanligt set</strong>
<span>Lägg till ett set</span>
</button>
<button className="set-type-option dropset" onClick={handleAddDropset}>
<strong>Dropset</strong>
<span>3 set med viktnedtrappning (20% per steg)</span>
</button>
<button className="set-type-cancel" onClick={() => setShowAddModal(false)}>
Avbryt
</button>
</div>
</div>
)}
</div>
)}
</div>
)
}
export default WorkoutPage