Critical Stability & Performance fixes. Excessive Log Set button gone on QIuck Log screen
This commit is contained in:
383
src/components/Tracker/ActiveSessionView.tsx
Normal file
383
src/components/Tracker/ActiveSessionView.tsx
Normal file
@@ -0,0 +1,383 @@
|
||||
import React from 'react';
|
||||
import { MoreVertical, X, CheckSquare, ChevronUp, ChevronDown, Scale, Dumbbell, Plus, Activity, Timer as TimerIcon, ArrowRight, ArrowUp, CheckCircle, Edit, Trash2 } from 'lucide-react';
|
||||
import { ExerciseType, Language, WorkoutSet } from '../../types';
|
||||
import { t } from '../../services/i18n';
|
||||
import FilledInput from '../FilledInput';
|
||||
import ExerciseModal from '../ExerciseModal';
|
||||
import { useTracker } from './useTracker';
|
||||
import SetLogger from './SetLogger';
|
||||
|
||||
interface ActiveSessionViewProps {
|
||||
tracker: ReturnType<typeof useTracker>;
|
||||
activeSession: any; // Using any to avoid strict type issues with the complex session object for now, but ideally should be WorkoutSession
|
||||
lang: Language;
|
||||
onSessionEnd: () => void;
|
||||
onSessionQuit: () => void;
|
||||
onRemoveSet: (setId: string) => void;
|
||||
}
|
||||
|
||||
const ActiveSessionView: React.FC<ActiveSessionViewProps> = ({ tracker, activeSession, lang, onSessionEnd, onSessionQuit, onRemoveSet }) => {
|
||||
const {
|
||||
elapsedTime,
|
||||
showFinishConfirm,
|
||||
setShowFinishConfirm,
|
||||
showQuitConfirm,
|
||||
setShowQuitConfirm,
|
||||
showMenu,
|
||||
setShowMenu,
|
||||
activePlan, // This comes from useTracker props but we might need to pass it explicitly if not in hook return
|
||||
currentStepIndex,
|
||||
showPlanList,
|
||||
setShowPlanList,
|
||||
jumpToStep,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
setShowSuggestions,
|
||||
showSuggestions,
|
||||
filteredExercises,
|
||||
setSelectedExercise,
|
||||
selectedExercise,
|
||||
weight,
|
||||
setWeight,
|
||||
reps,
|
||||
setReps,
|
||||
duration,
|
||||
setDuration,
|
||||
distance,
|
||||
setDistance,
|
||||
height,
|
||||
setHeight,
|
||||
handleAddSet,
|
||||
editingSetId,
|
||||
editWeight,
|
||||
setEditWeight,
|
||||
editReps,
|
||||
setEditReps,
|
||||
editDuration,
|
||||
setEditDuration,
|
||||
editDistance,
|
||||
setEditDistance,
|
||||
editHeight,
|
||||
setEditHeight,
|
||||
handleCancelEdit,
|
||||
handleSaveEdit,
|
||||
handleEditSet,
|
||||
isCreating,
|
||||
setIsCreating,
|
||||
handleCreateExercise,
|
||||
exercises
|
||||
} = tracker;
|
||||
|
||||
|
||||
|
||||
const isPlanFinished = activePlan && currentStepIndex >= activePlan.steps.length;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full max-h-full overflow-hidden relative bg-surface">
|
||||
<div className="px-4 py-3 bg-surface-container shadow-elevation-1 z-20 flex justify-between items-center">
|
||||
<div className="flex flex-col">
|
||||
<h2 className="text-title-medium text-on-surface flex items-center gap-2 font-medium">
|
||||
<span className="w-2 h-2 rounded-full bg-error animate-pulse" />
|
||||
{activePlan ? activePlan.name : t('free_workout', lang)}
|
||||
</h2>
|
||||
<span className="text-xs text-on-surface-variant font-mono mt-0.5 flex items-center gap-2">
|
||||
<span className="bg-surface-container-high px-1.5 py-0.5 rounded text-on-surface font-bold">{elapsedTime}</span>
|
||||
{activeSession.userBodyWeight ? ` • ${activeSession.userBodyWeight}kg` : ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 relative">
|
||||
<button
|
||||
onClick={() => setShowFinishConfirm(true)}
|
||||
className="px-5 py-2 rounded-full bg-error-container text-on-error-container text-sm font-medium hover:opacity-90 transition-opacity"
|
||||
>
|
||||
{t('finish', lang)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowMenu(!showMenu)}
|
||||
className="p-2 rounded-full bg-surface-container-high text-on-surface hover:bg-surface-container-highest transition-colors"
|
||||
>
|
||||
<MoreVertical size={20} />
|
||||
</button>
|
||||
{showMenu && (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 z-30"
|
||||
onClick={() => setShowMenu(false)}
|
||||
/>
|
||||
<div className="absolute right-0 top-full mt-2 bg-surface-container rounded-xl shadow-elevation-3 overflow-hidden z-40 min-w-[200px]">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowMenu(false);
|
||||
setShowQuitConfirm(true);
|
||||
}}
|
||||
className="w-full px-4 py-3 text-left text-error hover:bg-error-container/20 transition-colors flex items-center gap-2"
|
||||
>
|
||||
<X size={18} />
|
||||
{t('quit_no_save', lang)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{activePlan && (
|
||||
<div className="bg-surface-container-low border-b border-outline-variant">
|
||||
<button
|
||||
onClick={() => setShowPlanList(!showPlanList)}
|
||||
className="w-full px-4 py-3 flex justify-between items-center"
|
||||
>
|
||||
<div className="flex flex-col items-start">
|
||||
{isPlanFinished ? (
|
||||
<div className="flex items-center gap-2 text-primary">
|
||||
<CheckSquare size={18} />
|
||||
<span className="font-bold">{t('plan_completed', lang)}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<span className="text-[10px] text-primary font-medium tracking-wider">{t('step', lang)} {currentStepIndex + 1} {t('of', lang)} {activePlan.steps.length}</span>
|
||||
<div className="font-medium text-on-surface flex items-center gap-2">
|
||||
{activePlan.steps[currentStepIndex].exerciseName}
|
||||
{activePlan.steps[currentStepIndex].isWeighted && <Scale size={12} className="text-primary" />}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{showPlanList ? <ChevronUp size={20} className="text-on-surface-variant" /> : <ChevronDown size={20} className="text-on-surface-variant" />}
|
||||
</button>
|
||||
|
||||
{showPlanList && (
|
||||
<div className="max-h-48 overflow-y-auto bg-surface-container-high p-2 space-y-1 animate-in slide-in-from-top-2">
|
||||
{activePlan.steps.map((step, idx) => (
|
||||
<button
|
||||
key={step.id}
|
||||
onClick={() => jumpToStep(idx)}
|
||||
className={`w-full text-left px-4 py-3 rounded-full text-sm flex items-center justify-between transition-colors ${idx === currentStepIndex
|
||||
? 'bg-primary-container text-on-primary-container font-medium'
|
||||
: idx < currentStepIndex
|
||||
? 'text-on-surface-variant opacity-50'
|
||||
: 'text-on-surface hover:bg-white/5'
|
||||
}`}
|
||||
>
|
||||
<span>{idx + 1}. {step.exerciseName}</span>
|
||||
{step.isWeighted && <Scale size={14} />}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4 pb-32 space-y-6">
|
||||
|
||||
<SetLogger
|
||||
tracker={tracker}
|
||||
lang={lang}
|
||||
onLogSet={handleAddSet}
|
||||
/>
|
||||
|
||||
{activeSession.sets.length > 0 && (
|
||||
<div className="pt-4">
|
||||
<h3 className="text-sm text-primary font-medium px-2 mb-3 tracking-wide">{t('history_section', lang)}</h3>
|
||||
<div className="flex flex-col gap-2">
|
||||
{[...activeSession.sets].reverse().map((set: WorkoutSet, idx: number) => {
|
||||
const setNumber = activeSession.sets.length - idx;
|
||||
const isEditing = editingSetId === set.id;
|
||||
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 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">
|
||||
{setNumber}
|
||||
</div>
|
||||
{isEditing ? (
|
||||
<div className="flex-1">
|
||||
<div className="text-base font-medium text-on-surface mb-2">{set.exerciseName}{set.side && <span className="ml-2 text-xs font-medium text-on-surface-variant">{t(set.side.toLowerCase() as any, lang)}</span>}</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{set.weight !== undefined && (
|
||||
<input
|
||||
type="number"
|
||||
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 className="text-base font-medium text-on-surface">{set.exerciseName}{set.side && <span className="ml-2 text-xs font-medium text-on-surface-variant">{t(set.side.toLowerCase() as any, lang)}</span>}</div>
|
||||
<div className="text-sm text-on-surface-variant">
|
||||
{set.type === ExerciseType.STRENGTH &&
|
||||
`${set.weight ? `${set.weight}kg` : ''} ${set.reps ? `x ${set.reps}` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.BODYWEIGHT &&
|
||||
`${set.weight ? `${set.weight}kg` : ''} ${set.reps ? `x ${set.reps}` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.CARDIO &&
|
||||
`${set.durationSeconds ? `${set.durationSeconds}s` : ''} ${set.distanceMeters ? `/ ${set.distanceMeters}m` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.STATIC &&
|
||||
`${set.durationSeconds ? `${set.durationSeconds}s` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.HIGH_JUMP &&
|
||||
`${set.height ? `${set.height}cm` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.LONG_JUMP &&
|
||||
`${set.distanceMeters ? `${set.distanceMeters}m` : ''}`.trim()
|
||||
}
|
||||
{set.type === ExerciseType.PLYOMETRIC &&
|
||||
`${set.reps ? `x ${set.reps}` : ''}`.trim()
|
||||
}
|
||||
</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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isCreating && (
|
||||
<ExerciseModal
|
||||
isOpen={isCreating}
|
||||
onClose={() => setIsCreating(false)}
|
||||
onSave={handleCreateExercise}
|
||||
lang={lang}
|
||||
existingExercises={exercises}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Finish Confirmation Dialog */}
|
||||
{showFinishConfirm && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
||||
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
||||
<h3 className="text-2xl font-normal text-on-surface mb-2">{t('finish_confirm_title', lang)}</h3>
|
||||
<p className="text-on-surface-variant text-sm mb-8">{t('finish_confirm_msg', lang)}</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setShowFinishConfirm(false)}
|
||||
className="px-6 py-2.5 rounded-full text-primary font-medium hover:bg-white/5"
|
||||
>
|
||||
{t('cancel', lang)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowFinishConfirm(false);
|
||||
onSessionEnd();
|
||||
}}
|
||||
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
||||
>
|
||||
{t('confirm', lang)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Quit Without Saving Confirmation Dialog */}
|
||||
{showQuitConfirm && (
|
||||
<div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center p-6 backdrop-blur-sm">
|
||||
<div className="w-full max-w-sm bg-surface-container rounded-[28px] p-6 shadow-elevation-3">
|
||||
<h3 className="text-2xl font-normal text-error mb-2">{t('quit_confirm_title', lang)}</h3>
|
||||
<p className="text-on-surface-variant text-sm mb-8">{t('quit_confirm_msg', lang)}</p>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setShowQuitConfirm(false)}
|
||||
className="px-6 py-2.5 rounded-full text-primary font-medium hover:bg-white/5"
|
||||
>
|
||||
{t('cancel', lang)}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowQuitConfirm(false);
|
||||
onSessionQuit();
|
||||
}}
|
||||
className="px-6 py-2.5 rounded-full bg-green-600 text-white font-medium hover:bg-green-700"
|
||||
>
|
||||
{t('confirm', lang)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActiveSessionView;
|
||||
Reference in New Issue
Block a user