feat(04-03-partial): ExercisePicker and WorkoutEditPage components - swap/add/remove exercises with sets/reps editing
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
import { useState } from 'react'
|
||||
import { Icon } from '../components/Icons'
|
||||
import ExercisePicker from '../components/ExercisePicker'
|
||||
import './WorkoutEditPage.css'
|
||||
|
||||
export default function WorkoutEditPage({ workout, onBack, onSave }) {
|
||||
const [exercises, setExercises] = useState(workout.exercises || [])
|
||||
const [pickerOpen, setPickerOpen] = useState(false)
|
||||
const [swapIndex, setSwapIndex] = useState(null) // null = adding, number = swapping
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const handleOpenPicker = (index = null) => {
|
||||
setSwapIndex(index)
|
||||
setPickerOpen(true)
|
||||
}
|
||||
|
||||
const handleSelectExercise = (exercise) => {
|
||||
if (swapIndex !== null) {
|
||||
// Swap
|
||||
setExercises(prev => prev.map((ex, i) => {
|
||||
if (i === swapIndex) {
|
||||
return {
|
||||
...ex,
|
||||
exercise_id: exercise.id,
|
||||
name: exercise.name,
|
||||
muscle_group: exercise.muscle_group,
|
||||
// Keep existing sets/reps
|
||||
}
|
||||
}
|
||||
return ex
|
||||
}))
|
||||
} else {
|
||||
// Add
|
||||
setExercises(prev => [...prev, {
|
||||
exercise_id: exercise.id,
|
||||
name: exercise.name,
|
||||
muscle_group: exercise.muscle_group,
|
||||
sets: 3,
|
||||
reps_min: 8,
|
||||
reps_max: 12
|
||||
}])
|
||||
}
|
||||
setPickerOpen(false)
|
||||
}
|
||||
|
||||
const handleRemove = (index) => {
|
||||
setExercises(prev => prev.filter((_, i) => i !== index))
|
||||
}
|
||||
|
||||
const handleUpdate = (index, field, value) => {
|
||||
setExercises(prev => prev.map((ex, i) => {
|
||||
if (i === index) {
|
||||
return { ...ex, [field]: value }
|
||||
}
|
||||
return ex
|
||||
}))
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
setSaving(true)
|
||||
try {
|
||||
// Format for API
|
||||
const payload = {
|
||||
exercises: exercises.map(ex => ({
|
||||
exercise_id: ex.exercise_id || ex.id, // Handle both structures
|
||||
sets: parseInt(ex.sets) || 3,
|
||||
reps_min: parseInt(ex.reps_min) || 8,
|
||||
reps_max: parseInt(ex.reps_max) || 12
|
||||
}))
|
||||
}
|
||||
await onSave(workout.id, payload)
|
||||
} catch (err) {
|
||||
console.error('Failed to save workout:', err)
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="edit-page">
|
||||
<header className="page-header">
|
||||
<button className="back-btn" onClick={onBack}>
|
||||
<Icon name="arrowLeft" size={18} /> Avbryt
|
||||
</button>
|
||||
<h1>Redigera pass</h1>
|
||||
<button
|
||||
className="save-header-btn"
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
>
|
||||
{saving ? 'Sparar...' : 'Spara'}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<main className="edit-main">
|
||||
<div className="workout-meta-card">
|
||||
<h2>{workout.name}</h2>
|
||||
<p>{exercises.length} övningar</p>
|
||||
</div>
|
||||
|
||||
<div className="edit-exercises-list">
|
||||
{exercises.map((ex, i) => (
|
||||
<div key={i} className="edit-exercise-card">
|
||||
<div className="edit-card-header">
|
||||
<div className="edit-card-info">
|
||||
<h3>{ex.name}</h3>
|
||||
<span className="muscle-group">{ex.muscle_group}</span>
|
||||
</div>
|
||||
<div className="edit-card-actions">
|
||||
<button
|
||||
className="icon-btn"
|
||||
onClick={() => handleOpenPicker(i)}
|
||||
aria-label="Byt övning"
|
||||
>
|
||||
<Icon name="swap" size={18} />
|
||||
</button>
|
||||
<button
|
||||
className="icon-btn delete"
|
||||
onClick={() => handleRemove(i)}
|
||||
aria-label="Ta bort övning"
|
||||
>
|
||||
<Icon name="trash" size={18} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="edit-card-settings">
|
||||
<div className="setting-group">
|
||||
<label>Set</label>
|
||||
<input
|
||||
type="number"
|
||||
value={ex.sets}
|
||||
onChange={e => handleUpdate(i, 'sets', e.target.value)}
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
<div className="setting-group">
|
||||
<label>Reps min</label>
|
||||
<input
|
||||
type="number"
|
||||
value={ex.reps_min}
|
||||
onChange={e => handleUpdate(i, 'reps_min', e.target.value)}
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
<div className="setting-group">
|
||||
<label>Reps max</label>
|
||||
<input
|
||||
type="number"
|
||||
value={ex.reps_max}
|
||||
onChange={e => handleUpdate(i, 'reps_max', e.target.value)}
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="add-exercise-btn" onClick={() => handleOpenPicker(null)}>
|
||||
<Icon name="plus" size={20} />
|
||||
Lägg till övning
|
||||
</button>
|
||||
</main>
|
||||
|
||||
{pickerOpen && (
|
||||
<ExercisePicker
|
||||
open={pickerOpen}
|
||||
onSelect={handleSelectExercise}
|
||||
onClose={() => setPickerOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user