1. Removed Prev. and Body Weight % elements from the session logging screen. 2. Added logged set editing.
This commit is contained in:
13
App.tsx
13
App.tsx
@@ -134,6 +134,18 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateSetInActive = (updatedSet: WorkoutSet) => {
|
||||||
|
if (activeSession) {
|
||||||
|
setActiveSession(prev => {
|
||||||
|
if (!prev) return null;
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
sets: prev.sets.map(s => s.id === updatedSet.id ? updatedSet : s)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUpdateSession = (updatedSession: WorkoutSession) => {
|
const handleUpdateSession = (updatedSession: WorkoutSession) => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
saveSession(currentUser.id, updatedSession);
|
saveSession(currentUser.id, updatedSession);
|
||||||
@@ -169,6 +181,7 @@ function App() {
|
|||||||
onSessionEnd={handleEndSession}
|
onSessionEnd={handleEndSession}
|
||||||
onSetAdded={handleAddSet}
|
onSetAdded={handleAddSet}
|
||||||
onRemoveSet={handleRemoveSetFromActive}
|
onRemoveSet={handleRemoveSetFromActive}
|
||||||
|
onUpdateSet={handleUpdateSetInActive}
|
||||||
lang={language}
|
lang={language}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Plus, Activity, ChevronDown, ChevronUp, Dumbbell, PlayCircle, CheckCircle, User, Scale, X, Flame, Timer as TimerIcon, ArrowUp, ArrowRight, Footprints, Ruler, CheckSquare, Trash2, Percent, MoreVertical } from 'lucide-react';
|
import { Plus, Activity, ChevronDown, ChevronUp, Dumbbell, PlayCircle, CheckCircle, User, Scale, X, Flame, Timer as TimerIcon, ArrowUp, ArrowRight, Footprints, Ruler, CheckSquare, Trash2, Percent, MoreVertical, Edit } from 'lucide-react';
|
||||||
import { WorkoutSession, WorkoutSet, ExerciseDef, ExerciseType, WorkoutPlan, Language } from '../types';
|
import { WorkoutSession, WorkoutSet, ExerciseDef, ExerciseType, WorkoutPlan, Language } from '../types';
|
||||||
import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../services/storage';
|
import { getExercises, getLastSetForExercise, saveExercise, getPlans } from '../services/storage';
|
||||||
import { getCurrentUserProfile } from '../services/auth';
|
import { getCurrentUserProfile } from '../services/auth';
|
||||||
@@ -15,13 +15,14 @@ interface TrackerProps {
|
|||||||
onSessionEnd: () => void;
|
onSessionEnd: () => void;
|
||||||
onSetAdded: (set: WorkoutSet) => void;
|
onSetAdded: (set: WorkoutSet) => void;
|
||||||
onRemoveSet: (setId: string) => void;
|
onRemoveSet: (setId: string) => void;
|
||||||
|
onUpdateSet: (set: WorkoutSet) => void;
|
||||||
lang: Language;
|
lang: Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
import FilledInput from './FilledInput';
|
import FilledInput from './FilledInput';
|
||||||
import ExerciseModal from './ExerciseModal';
|
import ExerciseModal from './ExerciseModal';
|
||||||
|
|
||||||
const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSetAdded, onRemoveSet, lang }) => {
|
const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, activePlan, onSessionStart, onSessionEnd, onSetAdded, onRemoveSet, onUpdateSet, lang }) => {
|
||||||
const [exercises, setExercises] = useState<ExerciseDef[]>([]);
|
const [exercises, setExercises] = useState<ExerciseDef[]>([]);
|
||||||
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
const [plans, setPlans] = useState<WorkoutPlan[]>([]);
|
||||||
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
const [selectedExercise, setSelectedExercise] = useState<ExerciseDef | null>(null);
|
||||||
@@ -54,6 +55,14 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
const [showQuitConfirm, setShowQuitConfirm] = useState(false);
|
const [showQuitConfirm, setShowQuitConfirm] = useState(false);
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
const [showMenu, setShowMenu] = useState(false);
|
||||||
|
|
||||||
|
// Edit Set State
|
||||||
|
const [editingSetId, setEditingSetId] = useState<string | null>(null);
|
||||||
|
const [editWeight, setEditWeight] = useState<string>('');
|
||||||
|
const [editReps, setEditReps] = useState<string>('');
|
||||||
|
const [editDuration, setEditDuration] = useState<string>('');
|
||||||
|
const [editDistance, setEditDistance] = useState<string>('');
|
||||||
|
const [editHeight, setEditHeight] = useState<string>('');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
const exList = await getExercises(userId);
|
const exList = await getExercises(userId);
|
||||||
@@ -174,6 +183,32 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
setIsCreating(false);
|
setIsCreating(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEditSet = (set: WorkoutSet) => {
|
||||||
|
setEditingSetId(set.id);
|
||||||
|
setEditWeight(set.weight?.toString() || '');
|
||||||
|
setEditReps(set.reps?.toString() || '');
|
||||||
|
setEditDuration(set.durationSeconds?.toString() || '');
|
||||||
|
setEditDistance(set.distanceMeters?.toString() || '');
|
||||||
|
setEditHeight(set.height?.toString() || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveEdit = (set: WorkoutSet) => {
|
||||||
|
const updatedSet: WorkoutSet = {
|
||||||
|
...set,
|
||||||
|
...(editWeight && { weight: parseFloat(editWeight) }),
|
||||||
|
...(editReps && { reps: parseInt(editReps) }),
|
||||||
|
...(editDuration && { durationSeconds: parseInt(editDuration) }),
|
||||||
|
...(editDistance && { distanceMeters: parseFloat(editDistance) }),
|
||||||
|
...(editHeight && { height: parseFloat(editHeight) })
|
||||||
|
};
|
||||||
|
onUpdateSet(updatedSet);
|
||||||
|
setEditingSetId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelEdit = () => {
|
||||||
|
setEditingSetId(null);
|
||||||
|
};
|
||||||
|
|
||||||
const jumpToStep = (index: number) => {
|
const jumpToStep = (index: number) => {
|
||||||
if (!activePlan) return;
|
if (!activePlan) return;
|
||||||
setCurrentStepIndex(index);
|
setCurrentStepIndex(index);
|
||||||
@@ -446,22 +481,6 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
|
||||||
<div className="flex items-center gap-4 px-2">
|
|
||||||
<div className="flex items-center gap-2 text-on-surface-variant">
|
|
||||||
<Percent size={16} />
|
|
||||||
<span className="text-xs font-medium">{t('body_weight_percent', lang)}</span>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
className="w-20 border-b border-outline-variant bg-transparent text-center text-on-surface focus:border-primary focus:outline-none"
|
|
||||||
value={bwPercentage}
|
|
||||||
onChange={(e) => setBwPercentage(e.target.value)}
|
|
||||||
/>
|
|
||||||
<span className="text-on-surface-variant text-sm">%</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleAddSet}
|
onClick={handleAddSet}
|
||||||
className="w-full h-14 bg-primary-container text-on-primary-container font-medium text-lg rounded-full shadow-elevation-2 hover:shadow-elevation-3 active:scale-[0.98] transition-all flex items-center justify-center gap-2"
|
className="w-full h-14 bg-primary-container text-on-primary-container font-medium text-lg rounded-full shadow-elevation-2 hover:shadow-elevation-3 active:scale-[0.98] transition-all flex items-center justify-center gap-2"
|
||||||
@@ -469,22 +488,6 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
<CheckCircle size={24} />
|
<CheckCircle size={24} />
|
||||||
<span>{t('log_set', lang)}</span>
|
<span>{t('log_set', lang)}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<div className="bg-surface-container px-4 py-2 rounded-full border border-outline-variant/20 text-xs text-on-surface-variant">
|
|
||||||
{t('prev', lang)}: <span className="text-on-surface font-medium ml-1">
|
|
||||||
{lastSet ? (
|
|
||||||
<>
|
|
||||||
{lastSet?.weight ? `${lastSet?.weight}kg × ` : ''}
|
|
||||||
{lastSet?.reps ? `${lastSet?.reps}` : ''}
|
|
||||||
{lastSet?.distanceMeters ? `${lastSet?.distanceMeters}m` : ''}
|
|
||||||
{lastSet?.height ? `${lastSet?.height}cm` : ''}
|
|
||||||
{lastSet?.durationSeconds ? `${lastSet?.durationSeconds}s` : ''}
|
|
||||||
</>
|
|
||||||
) : '—'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -494,29 +497,113 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
{[...activeSession.sets].reverse().map((set, idx) => {
|
{[...activeSession.sets].reverse().map((set, idx) => {
|
||||||
const setNumber = activeSession.sets.length - idx;
|
const setNumber = activeSession.sets.length - idx;
|
||||||
|
const isEditing = editingSetId === set.id;
|
||||||
return (
|
return (
|
||||||
<div key={set.id} className="flex justify-between items-center p-4 bg-surface-container rounded-xl shadow-elevation-1 animate-in fade-in slide-in-from-bottom-2">
|
<div key={set.id} className="flex justify-between items-center p-4 bg-surface-container rounded-xl shadow-elevation-1 animate-in fade-in slide-in-from-bottom-2">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4 flex-1">
|
||||||
<div className="w-8 h-8 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center text-xs font-bold">
|
<div className="w-8 h-8 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center text-xs font-bold">
|
||||||
{setNumber}
|
{setNumber}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{isEditing ? (
|
||||||
<div className="text-base font-medium text-on-surface">{set.exerciseName}</div>
|
<div className="flex-1">
|
||||||
<div className="text-sm text-on-surface-variant">
|
<div className="text-base font-medium text-on-surface mb-2">{set.exerciseName}</div>
|
||||||
{set.weight !== undefined && `${set.weight}kg `}
|
<div className="grid grid-cols-2 gap-2">
|
||||||
{set.reps !== undefined && `x ${set.reps}`}
|
{set.weight !== undefined && (
|
||||||
{set.distanceMeters !== undefined && `${set.distanceMeters}m`}
|
<input
|
||||||
{set.durationSeconds !== undefined && `${set.durationSeconds}s`}
|
type="number"
|
||||||
{set.height !== undefined && `${set.height}cm`}
|
step="0.1"
|
||||||
|
value={editWeight}
|
||||||
|
onChange={(e) => setEditWeight(e.target.value)}
|
||||||
|
className="px-2 py-1 bg-surface-container-high rounded text-sm text-on-surface border border-outline-variant focus:border-primary focus:outline-none"
|
||||||
|
placeholder="Weight (kg)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{set.reps !== undefined && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={editReps}
|
||||||
|
onChange={(e) => setEditReps(e.target.value)}
|
||||||
|
className="px-2 py-1 bg-surface-container-high rounded text-sm text-on-surface border border-outline-variant focus:border-primary focus:outline-none"
|
||||||
|
placeholder="Reps"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{set.durationSeconds !== undefined && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
value={editDuration}
|
||||||
|
onChange={(e) => setEditDuration(e.target.value)}
|
||||||
|
className="px-2 py-1 bg-surface-container-high rounded text-sm text-on-surface border border-outline-variant focus:border-primary focus:outline-none"
|
||||||
|
placeholder="Duration (s)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{set.distanceMeters !== undefined && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
value={editDistance}
|
||||||
|
onChange={(e) => setEditDistance(e.target.value)}
|
||||||
|
className="px-2 py-1 bg-surface-container-high rounded text-sm text-on-surface border border-outline-variant focus:border-primary focus:outline-none"
|
||||||
|
placeholder="Distance (m)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{set.height !== undefined && (
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
step="0.1"
|
||||||
|
value={editHeight}
|
||||||
|
onChange={(e) => setEditHeight(e.target.value)}
|
||||||
|
className="px-2 py-1 bg-surface-container-high rounded text-sm text-on-surface border border-outline-variant focus:border-primary focus:outline-none"
|
||||||
|
placeholder="Height (cm)"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<div>
|
||||||
|
<div className="text-base font-medium text-on-surface">{set.exerciseName}</div>
|
||||||
|
<div className="text-sm text-on-surface-variant">
|
||||||
|
{set.weight !== undefined && `${set.weight}kg `}
|
||||||
|
{set.reps !== undefined && `x ${set.reps}`}
|
||||||
|
{set.distanceMeters !== undefined && `${set.distanceMeters}m`}
|
||||||
|
{set.durationSeconds !== undefined && `${set.durationSeconds}s`}
|
||||||
|
{set.height !== undefined && `${set.height}cm`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{isEditing ? (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleCancelEdit}
|
||||||
|
className="p-2 text-on-surface-variant hover:text-on-surface hover:bg-surface-container-high rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<X size={20} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleSaveEdit(set)}
|
||||||
|
className="p-2 text-primary hover:bg-primary-container/20 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<CheckCircle size={20} />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => handleEditSet(set)}
|
||||||
|
className="p-2 text-on-surface-variant hover:text-primary hover:bg-primary-container/20 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<Edit size={20} />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => onRemoveSet(set.id)}
|
||||||
|
className="p-2 text-on-surface-variant hover:text-error hover:bg-error-container/10 rounded-full transition-colors"
|
||||||
|
>
|
||||||
|
<Trash2 size={20} />
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
onClick={() => onRemoveSet(set.id)}
|
|
||||||
className="p-2 text-on-surface-variant hover:text-error hover:bg-error-container/10 rounded-full transition-colors"
|
|
||||||
>
|
|
||||||
<Trash2 size={20} />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -552,7 +639,7 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
setShowFinishConfirm(false);
|
setShowFinishConfirm(false);
|
||||||
onSessionEnd();
|
onSessionEnd();
|
||||||
}}
|
}}
|
||||||
className="px-6 py-2.5 rounded-full bg-primary text-on-primary font-medium"
|
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
||||||
>
|
>
|
||||||
{t('confirm', lang)}
|
{t('confirm', lang)}
|
||||||
</button>
|
</button>
|
||||||
@@ -580,7 +667,7 @@ const Tracker: React.FC<TrackerProps> = ({ userId, userWeight, activeSession, ac
|
|||||||
// Quit without saving - just navigate away or reset
|
// Quit without saving - just navigate away or reset
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}}
|
}}
|
||||||
className="px-6 py-2.5 rounded-full bg-error text-on-error font-medium"
|
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
||||||
>
|
>
|
||||||
{t('confirm', lang)}
|
{t('confirm', lang)}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user