Unilateral exercises logging
This commit is contained in:
@@ -17,6 +17,7 @@ const ExerciseModal: React.FC<ExerciseModalProps> = ({ isOpen, onClose, onSave,
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newType, setNewType] = useState<ExerciseType>(ExerciseType.STRENGTH);
|
||||
const [newBwPercentage, setNewBwPercentage] = useState<string>('100');
|
||||
const [isUnilateral, setIsUnilateral] = useState(false);
|
||||
const [error, setError] = useState<string>('');
|
||||
|
||||
const exerciseTypeLabels: Record<ExerciseType, string> = {
|
||||
@@ -47,12 +48,14 @@ const ExerciseModal: React.FC<ExerciseModalProps> = ({ isOpen, onClose, onSave,
|
||||
id: generateId(),
|
||||
name: trimmedName,
|
||||
type: newType,
|
||||
isUnilateral,
|
||||
...(newType === ExerciseType.BODYWEIGHT && { bodyWeightPercentage: parseFloat(newBwPercentage) || 100 })
|
||||
};
|
||||
await onSave(newEx);
|
||||
setNewName('');
|
||||
setNewType(ExerciseType.STRENGTH);
|
||||
setNewBwPercentage('100');
|
||||
setIsUnilateral(false);
|
||||
setError('');
|
||||
onClose();
|
||||
};
|
||||
@@ -120,6 +123,19 @@ const ExerciseModal: React.FC<ExerciseModalProps> = ({ isOpen, onClose, onSave,
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 px-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="isUnilateral"
|
||||
checked={isUnilateral}
|
||||
onChange={(e) => setIsUnilateral(e.target.checked)}
|
||||
className="w-5 h-5 rounded border-2 border-outline bg-surface-container-high checked:bg-primary checked:border-primary cursor-pointer"
|
||||
/>
|
||||
<label htmlFor="isUnilateral" className="text-sm text-on-surface cursor-pointer">
|
||||
{t('unilateral_exercise', lang) || 'Unilateral exercise (separate left/right tracking)'}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end mt-4">
|
||||
<button
|
||||
onClick={handleCreateExercise}
|
||||
|
||||
@@ -405,7 +405,10 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
<div key={ex.id} className={`p-3 rounded-lg flex justify-between items-center border border-outline-variant/20 ${ex.isArchived ? 'bg-surface-container-low opacity-60' : 'bg-surface-container-high'}`}>
|
||||
<div className="overflow-hidden mr-2">
|
||||
<div className="font-medium text-sm text-on-surface truncate">{ex.name}</div>
|
||||
<div className="text-xs text-on-surface-variant">{exerciseTypeLabels[ex.type]}</div>
|
||||
<div className="text-xs text-on-surface-variant">
|
||||
{exerciseTypeLabels[ex.type]}
|
||||
{ex.isUnilateral && `, ${t('unilateral', lang)}`}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
<button onClick={() => setEditingExercise(ex)} className="p-2 text-on-surface-variant hover:text-primary hover:bg-white/5 rounded-full">
|
||||
|
||||
@@ -227,51 +227,178 @@ const ActiveSessionView: React.FC<ActiveSessionViewProps> = ({ tracker, activeSe
|
||||
|
||||
{selectedExercise && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-300 space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={weight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => setWeight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
autoFocus={activePlan && !isPlanFinished && activePlan.steps[currentStepIndex]?.isWeighted && (selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STRENGTH)}
|
||||
{/* Unilateral Exercise Toggle */}
|
||||
{selectedExercise.isUnilateral && (
|
||||
<div className="flex items-center gap-3 px-2 py-3 bg-surface-container rounded-xl">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="sameValuesBothSides"
|
||||
checked={tracker.sameValuesBothSides}
|
||||
onChange={(e) => tracker.handleToggleSameValues(e.target.checked)}
|
||||
className="w-5 h-5 rounded border-2 border-outline bg-surface-container-high checked:bg-primary checked:border-primary cursor-pointer"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={reps}
|
||||
onChange={(e: any) => setReps(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={duration}
|
||||
onChange={(e: any) => setDuration(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={distance}
|
||||
onChange={(e: any) => setDistance(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={height}
|
||||
onChange={(e: any) => setHeight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="sameValuesBothSides" className="text-sm text-on-surface cursor-pointer flex-1">
|
||||
{t('same_values_both_sides', lang)}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input Forms */}
|
||||
{selectedExercise.isUnilateral && !tracker.sameValuesBothSides ? (
|
||||
/* Separate Left/Right Inputs */
|
||||
<div className="space-y-4">
|
||||
{/* Left Side */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-primary flex items-center gap-2 px-2">
|
||||
<span className="w-6 h-6 rounded-full bg-primary-container text-on-primary-container flex items-center justify-center text-xs font-bold">L</span>
|
||||
{t('left', lang)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={tracker.weightLeft}
|
||||
step="0.1"
|
||||
onChange={(e: any) => tracker.setWeightLeft(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={tracker.repsLeft}
|
||||
onChange={(e: any) => tracker.setRepsLeft(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={tracker.durationLeft}
|
||||
onChange={(e: any) => tracker.setDurationLeft(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={tracker.distanceLeft}
|
||||
onChange={(e: any) => tracker.setDistanceLeft(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={tracker.heightLeft}
|
||||
onChange={(e: any) => tracker.setHeightLeft(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-secondary flex items-center gap-2 px-2">
|
||||
<span className="w-6 h-6 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center text-xs font-bold">R</span>
|
||||
{t('right', lang)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={tracker.weightRight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => tracker.setWeightRight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={tracker.repsRight}
|
||||
onChange={(e: any) => tracker.setRepsRight(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={tracker.durationRight}
|
||||
onChange={(e: any) => tracker.setDurationRight(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={tracker.distanceRight}
|
||||
onChange={(e: any) => tracker.setDistanceRight(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={tracker.heightRight}
|
||||
onChange={(e: any) => tracker.setHeightRight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Single Input Form (for bilateral or unilateral with same values) */
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={weight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => setWeight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
autoFocus={activePlan && !isPlanFinished && activePlan.steps[currentStepIndex]?.isWeighted && (selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STRENGTH)}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={reps}
|
||||
onChange={(e: any) => setReps(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={duration}
|
||||
onChange={(e: any) => setDuration(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={distance}
|
||||
onChange={(e: any) => setDistance(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={height}
|
||||
onChange={(e: any) => setHeight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleAddSet}
|
||||
|
||||
@@ -143,50 +143,177 @@ const SporadicView: React.FC<SporadicViewProps> = ({ tracker, lang, sporadicSets
|
||||
|
||||
{selectedExercise && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-4 duration-300 space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={weight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => setWeight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
{/* Unilateral Exercise Toggle */}
|
||||
{selectedExercise.isUnilateral && (
|
||||
<div className="flex items-center gap-3 px-2 py-3 bg-surface-container rounded-xl">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="sameValuesBothSidesSporadic"
|
||||
checked={tracker.sameValuesBothSides}
|
||||
onChange={(e) => tracker.handleToggleSameValues(e.target.checked)}
|
||||
className="w-5 h-5 rounded border-2 border-outline bg-surface-container-high checked:bg-primary checked:border-primary cursor-pointer"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={reps}
|
||||
onChange={(e: any) => setReps(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={duration}
|
||||
onChange={(e: any) => setDuration(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={distance}
|
||||
onChange={(e: any) => setDistance(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={height}
|
||||
onChange={(e: any) => setHeight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<label htmlFor="sameValuesBothSidesSporadic" className="text-sm text-on-surface cursor-pointer flex-1">
|
||||
{t('same_values_both_sides', lang)}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Input Forms */}
|
||||
{selectedExercise.isUnilateral && !tracker.sameValuesBothSides ? (
|
||||
/* Separate Left/Right Inputs */
|
||||
<div className="space-y-4">
|
||||
{/* Left Side */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-primary flex items-center gap-2 px-2">
|
||||
<span className="w-6 h-6 rounded-full bg-primary-container text-on-primary-container flex items-center justify-center text-xs font-bold">L</span>
|
||||
{t('left', lang)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={tracker.weightLeft}
|
||||
step="0.1"
|
||||
onChange={(e: any) => tracker.setWeightLeft(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={tracker.repsLeft}
|
||||
onChange={(e: any) => tracker.setRepsLeft(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={tracker.durationLeft}
|
||||
onChange={(e: any) => tracker.setDurationLeft(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={tracker.distanceLeft}
|
||||
onChange={(e: any) => tracker.setDistanceLeft(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={tracker.heightLeft}
|
||||
onChange={(e: any) => tracker.setHeightLeft(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Side */}
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium text-secondary flex items-center gap-2 px-2">
|
||||
<span className="w-6 h-6 rounded-full bg-secondary-container text-on-secondary-container flex items-center justify-center text-xs font-bold">R</span>
|
||||
{t('right', lang)}
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={tracker.weightRight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => tracker.setWeightRight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={tracker.repsRight}
|
||||
onChange={(e: any) => tracker.setRepsRight(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={tracker.durationRight}
|
||||
onChange={(e: any) => tracker.setDurationRight(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={tracker.distanceRight}
|
||||
onChange={(e: any) => tracker.setDistanceRight(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={tracker.heightRight}
|
||||
onChange={(e: any) => tracker.setHeightRight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* Single Input Form (for bilateral or unilateral with same values) */
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={selectedExercise.type === ExerciseType.BODYWEIGHT ? t('add_weight', lang) : t('weight_kg', lang)}
|
||||
value={weight}
|
||||
step="0.1"
|
||||
onChange={(e: any) => setWeight(e.target.value)}
|
||||
icon={<Scale size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.STRENGTH || selectedExercise.type === ExerciseType.BODYWEIGHT || selectedExercise.type === ExerciseType.PLYOMETRIC) && (
|
||||
<FilledInput
|
||||
label={t('reps', lang)}
|
||||
value={reps}
|
||||
onChange={(e: any) => setReps(e.target.value)}
|
||||
icon={<Activity size={10} />}
|
||||
type="number"
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.STATIC) && (
|
||||
<FilledInput
|
||||
label={t('time_sec', lang)}
|
||||
value={duration}
|
||||
onChange={(e: any) => setDuration(e.target.value)}
|
||||
icon={<TimerIcon size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.CARDIO || selectedExercise.type === ExerciseType.LONG_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('dist_m', lang)}
|
||||
value={distance}
|
||||
onChange={(e: any) => setDistance(e.target.value)}
|
||||
icon={<ArrowRight size={10} />}
|
||||
/>
|
||||
)}
|
||||
{(selectedExercise.type === ExerciseType.HIGH_JUMP) && (
|
||||
<FilledInput
|
||||
label={t('height_cm', lang)}
|
||||
value={height}
|
||||
onChange={(e: any) => setHeight(e.target.value)}
|
||||
icon={<ArrowUp size={10} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleLogSporadicSet}
|
||||
|
||||
@@ -77,6 +77,36 @@ export const useTracker = ({
|
||||
const [isSporadicMode, setIsSporadicMode] = useState(false);
|
||||
const [sporadicSuccess, setSporadicSuccess] = useState(false);
|
||||
|
||||
// Unilateral Exercise State
|
||||
const [sameValuesBothSides, setSameValuesBothSides] = useState(true);
|
||||
const [weightLeft, setWeightLeft] = useState<string>('');
|
||||
const [weightRight, setWeightRight] = useState<string>('');
|
||||
const [repsLeft, setRepsLeft] = useState<string>('');
|
||||
const [repsRight, setRepsRight] = useState<string>('');
|
||||
const [durationLeft, setDurationLeft] = useState<string>('');
|
||||
const [durationRight, setDurationRight] = useState<string>('');
|
||||
const [distanceLeft, setDistanceLeft] = useState<string>('');
|
||||
const [distanceRight, setDistanceRight] = useState<string>('');
|
||||
const [heightLeft, setHeightLeft] = useState<string>('');
|
||||
const [heightRight, setHeightRight] = useState<string>('');
|
||||
|
||||
const handleToggleSameValues = (checked: boolean) => {
|
||||
setSameValuesBothSides(checked);
|
||||
if (!checked) {
|
||||
// Propagate values from single fields to left/right fields
|
||||
setWeightLeft(weight);
|
||||
setWeightRight(weight);
|
||||
setRepsLeft(reps);
|
||||
setRepsRight(reps);
|
||||
setDurationLeft(duration);
|
||||
setDurationRight(duration);
|
||||
setDistanceLeft(distance);
|
||||
setDistanceRight(distance);
|
||||
setHeightLeft(height);
|
||||
setHeightRight(height);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
const exList = await getExercises(userId);
|
||||
@@ -216,100 +246,289 @@ export const useTracker = ({
|
||||
const handleAddSet = async () => {
|
||||
if (!activeSession || !selectedExercise) return;
|
||||
|
||||
const setData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
};
|
||||
// For unilateral exercises, create two sets (LEFT and RIGHT)
|
||||
if (selectedExercise.isUnilateral) {
|
||||
const setsToCreate: Array<Partial<WorkoutSet> & { side: 'LEFT' | 'RIGHT' }> = [];
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) setData.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
if (sameValuesBothSides) {
|
||||
// Create two identical sets with LEFT and RIGHT sides
|
||||
const setData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await api.post('/sessions/active/log-set', setData);
|
||||
if (response.success) {
|
||||
const { newSet, activeExerciseId } = response;
|
||||
onSetAdded(newSet);
|
||||
|
||||
if (activePlan && activeExerciseId) {
|
||||
const nextStepIndex = activePlan.steps.findIndex(step => step.exerciseId === activeExerciseId);
|
||||
if (nextStepIndex !== -1) {
|
||||
setCurrentStepIndex(nextStepIndex);
|
||||
}
|
||||
} else if (activePlan && !activeExerciseId) {
|
||||
// Plan is finished
|
||||
setCurrentStepIndex(activePlan.steps.length);
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) setData.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
|
||||
setsToCreate.push({ ...setData, side: 'LEFT' });
|
||||
setsToCreate.push({ ...setData, side: 'RIGHT' });
|
||||
} else {
|
||||
// Create separate sets for LEFT and RIGHT with different values
|
||||
const leftSetData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
};
|
||||
const rightSetData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
};
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weightLeft) leftSetData.weight = parseFloat(weightLeft);
|
||||
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
||||
if (weightRight) rightSetData.weight = parseFloat(weightRight);
|
||||
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weightLeft) leftSetData.weight = parseFloat(weightLeft);
|
||||
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
||||
leftSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (weightRight) rightSetData.weight = parseFloat(weightRight);
|
||||
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
||||
rightSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (durationLeft) leftSetData.durationSeconds = parseInt(durationLeft);
|
||||
if (distanceLeft) leftSetData.distanceMeters = parseFloat(distanceLeft);
|
||||
if (durationRight) rightSetData.durationSeconds = parseInt(durationRight);
|
||||
if (distanceRight) rightSetData.distanceMeters = parseFloat(distanceRight);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (durationLeft) leftSetData.durationSeconds = parseInt(durationLeft);
|
||||
leftSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (durationRight) rightSetData.durationSeconds = parseInt(durationRight);
|
||||
rightSetData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (heightLeft) leftSetData.height = parseFloat(heightLeft);
|
||||
if (heightRight) rightSetData.height = parseFloat(heightRight);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distanceLeft) leftSetData.distanceMeters = parseFloat(distanceLeft);
|
||||
if (distanceRight) rightSetData.distanceMeters = parseFloat(distanceRight);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (repsLeft) leftSetData.reps = parseInt(repsLeft);
|
||||
if (repsRight) rightSetData.reps = parseInt(repsRight);
|
||||
break;
|
||||
}
|
||||
|
||||
setsToCreate.push({ ...leftSetData, side: 'LEFT' });
|
||||
setsToCreate.push({ ...rightSetData, side: 'RIGHT' });
|
||||
}
|
||||
|
||||
// Log both sets
|
||||
try {
|
||||
for (const setData of setsToCreate) {
|
||||
const response = await api.post('/sessions/active/log-set', setData);
|
||||
if (response.success) {
|
||||
const { newSet } = response;
|
||||
onSetAdded(newSet);
|
||||
}
|
||||
}
|
||||
|
||||
// Update plan progress after logging both sets
|
||||
if (activePlan) {
|
||||
const response = await api.post('/sessions/active/log-set', { exerciseId: selectedExercise.id });
|
||||
if (response.success && response.activeExerciseId) {
|
||||
const nextStepIndex = activePlan.steps.findIndex(step => step.exerciseId === response.activeExerciseId);
|
||||
if (nextStepIndex !== -1) {
|
||||
setCurrentStepIndex(nextStepIndex);
|
||||
}
|
||||
} else if (response.success && !response.activeExerciseId) {
|
||||
setCurrentStepIndex(activePlan.steps.length);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to log unilateral sets:", error);
|
||||
}
|
||||
} else {
|
||||
// Regular bilateral exercise - single set
|
||||
const setData: Partial<WorkoutSet> = {
|
||||
exerciseId: selectedExercise.id,
|
||||
};
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) setData.weight = parseFloat(weight);
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) setData.durationSeconds = parseInt(duration);
|
||||
setData.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) setData.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) setData.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) setData.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.post('/sessions/active/log-set', setData);
|
||||
if (response.success) {
|
||||
const { newSet, activeExerciseId } = response;
|
||||
onSetAdded(newSet);
|
||||
|
||||
if (activePlan && activeExerciseId) {
|
||||
const nextStepIndex = activePlan.steps.findIndex(step => step.exerciseId === activeExerciseId);
|
||||
if (nextStepIndex !== -1) {
|
||||
setCurrentStepIndex(nextStepIndex);
|
||||
}
|
||||
} else if (activePlan && !activeExerciseId) {
|
||||
// Plan is finished
|
||||
setCurrentStepIndex(activePlan.steps.length);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to log set:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to log set:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogSporadicSet = async () => {
|
||||
if (!selectedExercise) return;
|
||||
|
||||
const set: any = {
|
||||
exerciseId: selectedExercise.id,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
// For unilateral exercises, create two sets (LEFT and RIGHT)
|
||||
if (selectedExercise.isUnilateral) {
|
||||
const setsToCreate: any[] = [];
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) set.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
if (sameValuesBothSides) {
|
||||
// Create two identical sets with LEFT and RIGHT sides
|
||||
const set: any = {
|
||||
exerciseId: selectedExercise.id,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await logSporadicSet(set);
|
||||
if (result) {
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) set.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
|
||||
setsToCreate.push({ ...set, side: 'LEFT' });
|
||||
setsToCreate.push({ ...set, side: 'RIGHT' });
|
||||
} else {
|
||||
// Create separate sets for LEFT and RIGHT with different values
|
||||
const leftSet: any = {
|
||||
exerciseId: selectedExercise.id,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
const rightSet: any = {
|
||||
exerciseId: selectedExercise.id,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weightLeft) leftSet.weight = parseFloat(weightLeft);
|
||||
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
||||
if (weightRight) rightSet.weight = parseFloat(weightRight);
|
||||
if (repsRight) rightSet.reps = parseInt(repsRight);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weightLeft) leftSet.weight = parseFloat(weightLeft);
|
||||
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
||||
leftSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (weightRight) rightSet.weight = parseFloat(weightRight);
|
||||
if (repsRight) rightSet.reps = parseInt(repsRight);
|
||||
rightSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (durationLeft) leftSet.durationSeconds = parseInt(durationLeft);
|
||||
if (distanceLeft) leftSet.distanceMeters = parseFloat(distanceLeft);
|
||||
if (durationRight) rightSet.durationSeconds = parseInt(durationRight);
|
||||
if (distanceRight) rightSet.distanceMeters = parseFloat(distanceRight);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (durationLeft) leftSet.durationSeconds = parseInt(durationLeft);
|
||||
leftSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
if (durationRight) rightSet.durationSeconds = parseInt(durationRight);
|
||||
rightSet.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (heightLeft) leftSet.height = parseFloat(heightLeft);
|
||||
if (heightRight) rightSet.height = parseFloat(heightRight);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distanceLeft) leftSet.distanceMeters = parseFloat(distanceLeft);
|
||||
if (distanceRight) rightSet.distanceMeters = parseFloat(distanceRight);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (repsLeft) leftSet.reps = parseInt(repsLeft);
|
||||
if (repsRight) rightSet.reps = parseInt(repsRight);
|
||||
break;
|
||||
}
|
||||
|
||||
setsToCreate.push({ ...leftSet, side: 'LEFT' });
|
||||
setsToCreate.push({ ...rightSet, side: 'RIGHT' });
|
||||
}
|
||||
|
||||
// Log both sets
|
||||
try {
|
||||
for (const set of setsToCreate) {
|
||||
await logSporadicSet(set);
|
||||
}
|
||||
setSporadicSuccess(true);
|
||||
setTimeout(() => setSporadicSuccess(false), 2000);
|
||||
// Reset form
|
||||
@@ -318,10 +537,72 @@ export const useTracker = ({
|
||||
setDuration('');
|
||||
setDistance('');
|
||||
setHeight('');
|
||||
setWeightLeft('');
|
||||
setWeightRight('');
|
||||
setRepsLeft('');
|
||||
setRepsRight('');
|
||||
setDurationLeft('');
|
||||
setDurationRight('');
|
||||
setDistanceLeft('');
|
||||
setDistanceRight('');
|
||||
setHeightLeft('');
|
||||
setHeightRight('');
|
||||
if (onSporadicSetAdded) onSporadicSetAdded();
|
||||
} catch (error) {
|
||||
console.error("Failed to log unilateral sporadic sets:", error);
|
||||
}
|
||||
} else {
|
||||
// Regular bilateral exercise - single set
|
||||
const set: any = {
|
||||
exerciseId: selectedExercise.id,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
switch (selectedExercise.type) {
|
||||
case ExerciseType.STRENGTH:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
case ExerciseType.BODYWEIGHT:
|
||||
if (weight) set.weight = parseFloat(weight);
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.CARDIO:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.STATIC:
|
||||
if (duration) set.durationSeconds = parseInt(duration);
|
||||
set.bodyWeightPercentage = parseFloat(bwPercentage) || 100;
|
||||
break;
|
||||
case ExerciseType.HIGH_JUMP:
|
||||
if (height) set.height = parseFloat(height);
|
||||
break;
|
||||
case ExerciseType.LONG_JUMP:
|
||||
if (distance) set.distanceMeters = parseFloat(distance);
|
||||
break;
|
||||
case ExerciseType.PLYOMETRIC:
|
||||
if (reps) set.reps = parseInt(reps);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await logSporadicSet(set);
|
||||
if (result) {
|
||||
setSporadicSuccess(true);
|
||||
setTimeout(() => setSporadicSuccess(false), 2000);
|
||||
// Reset form
|
||||
setWeight('');
|
||||
setReps('');
|
||||
setDuration('');
|
||||
setDistance('');
|
||||
setHeight('');
|
||||
if (onSporadicSetAdded) onSporadicSetAdded();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to log sporadic set:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to log sporadic set:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -440,5 +721,29 @@ export const useTracker = ({
|
||||
handleCancelEdit,
|
||||
jumpToStep,
|
||||
resetForm,
|
||||
// Unilateral exercise state
|
||||
sameValuesBothSides,
|
||||
setSameValuesBothSides,
|
||||
weightLeft,
|
||||
setWeightLeft,
|
||||
weightRight,
|
||||
setWeightRight,
|
||||
repsLeft,
|
||||
setRepsLeft,
|
||||
repsRight,
|
||||
setRepsRight,
|
||||
durationLeft,
|
||||
setDurationLeft,
|
||||
durationRight,
|
||||
setDurationRight,
|
||||
distanceLeft,
|
||||
setDistanceLeft,
|
||||
distanceRight,
|
||||
setDistanceRight,
|
||||
heightLeft,
|
||||
setHeightLeft,
|
||||
heightRight,
|
||||
setHeightRight,
|
||||
handleToggleSameValues,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user