diff --git a/src/components/mesocycles/MesocycleDashboard.tsx b/src/components/mesocycles/MesocycleDashboard.tsx
index de43f06..3fcc293 100644
--- a/src/components/mesocycles/MesocycleDashboard.tsx
+++ b/src/components/mesocycles/MesocycleDashboard.tsx
@@ -117,6 +117,8 @@ export default function MesocycleDashboard({
};
const handleStartWorkout = (splitDayId: string) => {
+ // Clear any stale active workout so the new split takes priority
+ localStorage.removeItem('activeWorkout');
// Store the selected split day ID in localStorage for the workout session to pick up
localStorage.setItem('selectedSplitDayId', splitDayId);
// Navigate to workout page
diff --git a/src/components/mesocycles/SplitProgressTracker.css b/src/components/mesocycles/SplitProgressTracker.css
index 05604f9..08b7b57 100644
--- a/src/components/mesocycles/SplitProgressTracker.css
+++ b/src/components/mesocycles/SplitProgressTracker.css
@@ -191,7 +191,70 @@
margin: 0;
}
-/* Start Workout Button */
+/* Clickable Split Cards */
+.split-card.clickable {
+ cursor: pointer;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ font-family: inherit;
+ width: 100%;
+ /* Reset default button styles so clickable cards render consistently */
+ border: none;
+ background: none;
+ padding: 0;
+ color: inherit;
+ text-align: inherit;
+ appearance: none;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ outline: none;
+}
+
+.split-card.clickable:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+.split-card.clickable:active {
+ transform: translateY(0);
+ box-shadow: none;
+}
+
+.split-card.clickable:focus-visible {
+ outline: 2px solid #3b82f6;
+ outline-offset: 2px;
+}
+.split-card.in-progress {
+ background: #fffbeb;
+ border-color: #f59e0b;
+ box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.15);
+}
+
+.split-card.in-progress .split-status {
+ color: #d97706;
+ font-size: 0.875rem;
+}
+
+.split-card-action {
+ margin-top: 0.5rem;
+ font-size: 0.75rem;
+ color: #9ca3af;
+ font-weight: 500;
+}
+
+.split-card.next .split-card-action {
+ color: #3b82f6;
+}
+
+.split-card.in-progress .split-card-action {
+ color: #d97706;
+}
+
+.split-card.completed .split-card-action {
+ color: #10b981;
+}
+
+/* Start Workout Button (legacy, kept for compatibility) */
.start-workout-btn {
width: 100%;
margin-top: 1rem;
diff --git a/src/components/mesocycles/SplitProgressTracker.tsx b/src/components/mesocycles/SplitProgressTracker.tsx
index b66be54..fd2792e 100644
--- a/src/components/mesocycles/SplitProgressTracker.tsx
+++ b/src/components/mesocycles/SplitProgressTracker.tsx
@@ -141,16 +141,49 @@ export default function SplitProgressTracker({
{splitStatus.map((info) => {
const isNext = !allCompleted && info.splitDay.id === nextSplit?.id;
+ const isInProgress =
+ activeWorkout &&
+ !activeWorkout.completed &&
+ activeWorkout.splitDayId === info.splitDay.id;
+
+ const handleCardClick = () => {
+ if (isInProgress) {
+ if (onResumeWorkout) {
+ onResumeWorkout();
+ } else {
+ console.warn(
+ 'Resume workout callback not provided - workout cannot be resumed'
+ );
+ }
+ } else if (onStartWorkout) {
+ onStartWorkout(info.splitDay.id);
+ } else {
+ console.warn(
+ 'Start workout callback not provided - workout cannot be started'
+ );
+ }
+ };
return (
-
{info.splitDay.name}
- {info.completed ? '✓' : isNext ? '★ Next' : ''}
+ {isInProgress
+ ? '🏋️ In Progress'
+ : info.completed
+ ? '✓'
+ : isNext
+ ? '★ Next'
+ : ''}
{info.completedDate && (
@@ -164,28 +197,26 @@ export default function SplitProgressTracker({
{info.splitDay.exercises.length !== 1 ? 's' : ''}
)}
-
+
+ {isInProgress
+ ? 'Tap to resume'
+ : info.completed
+ ? 'Tap to redo'
+ : 'Tap to start'}
+
+
);
})}
- {/* Action Button */}
- {allCompleted ? (
+ {/* Completion Message */}
+ {allCompleted && (
🎉
All done this week!
-
- You can repeat splits or start next week's training
-
+
Tap any split above to redo it
- ) : nextSplit && onStartWorkout ? (
-
- ) : null}
+ )}
{/* Deload Week Message */}
diff --git a/src/components/workouts/WorkoutSession.tsx b/src/components/workouts/WorkoutSession.tsx
index acb593b..5f671e7 100644
--- a/src/components/workouts/WorkoutSession.tsx
+++ b/src/components/workouts/WorkoutSession.tsx
@@ -3,7 +3,7 @@
* Handles active workout logging and tracking
*/
-import { useState, useMemo, useEffect } from 'react';
+import { useState, useMemo, useEffect, useRef } from 'react';
import { useWorkoutSession } from '../../hooks/useWorkoutSession';
import {
useExercises,
@@ -103,11 +103,25 @@ export default function WorkoutSession({ onNavigate }: WorkoutSessionProps) {
}, [activeMesocycle, completedWorkouts]);
// Auto-start workout if coming from mesocycle dashboard with a selected split
+ const autoStartProcessedRef = useRef(false);
useEffect(() => {
const selectedSplitDayId = localStorage.getItem('selectedSplitDayId');
- if (selectedSplitDayId && activeMesocycle && !isActive) {
+ if (
+ selectedSplitDayId &&
+ activeMesocycle &&
+ !autoStartProcessedRef.current
+ ) {
+ // Mark as processed to prevent re-execution
+ autoStartProcessedRef.current = true;
// Clear immediately to prevent re-triggering
localStorage.removeItem('selectedSplitDayId');
+
+ // If there's already an active (but stale) workout, cancel it first
+ // so we start fresh with the user's explicitly chosen split
+ if (isActive) {
+ cancelWorkout();
+ }
+
// Auto-start the workout with the selected split
startWorkoutFromSplit(activeMesocycle.id, selectedSplitDayId).catch(
(error) => {
@@ -116,7 +130,13 @@ export default function WorkoutSession({ onNavigate }: WorkoutSessionProps) {
}
);
}
- }, [activeMesocycle, isActive, startWorkoutFromSplit, showToast]);
+ }, [
+ activeMesocycle,
+ isActive,
+ startWorkoutFromSplit,
+ cancelWorkout,
+ showToast,
+ ]);
// Get all unique muscle groups from workout exercises
const workoutMuscleGroups = useMemo(() => {
diff --git a/src/hooks/useWorkoutSession.ts b/src/hooks/useWorkoutSession.ts
index 344a67b..e46e07d 100644
--- a/src/hooks/useWorkoutSession.ts
+++ b/src/hooks/useWorkoutSession.ts
@@ -9,7 +9,6 @@ import type {
WorkoutExercise,
WorkoutSet,
WorkoutFeedback,
- MesocycleSplitDay,
} from '../types/models';
import {
createWorkout,
@@ -20,7 +19,6 @@ import {
createEmptySet,
getPreviousPerformance,
startWorkoutFromSplit as startWorkoutFromSplitService,
- getActiveMesocycle,
} from '../db/service';
interface UseWorkoutSessionReturn {
@@ -102,31 +100,10 @@ export function useWorkoutSession(): UseWorkoutSessionReturn {
};
}, [workout, isActive]);
- const startWorkout = useCallback(async () => {
- // Check if there's a selected split day from the dashboard
- const selectedSplitDayId = localStorage.getItem('selectedSplitDayId');
- let splitDayId: string | undefined;
- let splitDay: MesocycleSplitDay | undefined;
-
- if (selectedSplitDayId) {
- // Get the active mesocycle to find the split day
- const activeMesocycle = await getActiveMesocycle();
- if (activeMesocycle) {
- splitDay = activeMesocycle.splitDays.find(
- (sd: MesocycleSplitDay) => sd.id === selectedSplitDayId
- );
- if (splitDay) {
- splitDayId = selectedSplitDayId;
- }
- }
- // Clear the selected split day from localStorage
- localStorage.removeItem('selectedSplitDayId');
- }
-
+ const startWorkout = useCallback(() => {
const newWorkout: Workout = {
- id: 'temp-workout-' + crypto.randomUUID(), // Temporary ID until saved to DB
+ id: 'temp-workout-' + crypto.randomUUID(),
date: new Date(),
- splitDayId,
exercises: [],
notes: undefined,
completed: false,
@@ -138,41 +115,6 @@ export function useWorkoutSession(): UseWorkoutSessionReturn {
setWorkout(newWorkout);
setIsActive(true);
setCurrentExerciseIndex(0);
-
- // If we have a split day, auto-load its exercises
- if (splitDay && splitDay.exercises.length > 0) {
- // We'll load exercises asynchronously after the workout is created
- const exercisesWithSets: WorkoutExercise[] = [];
-
- for (const mesocycleExercise of splitDay.exercises) {
- // Get previous performance for this exercise
- const previousPerformance = await getPreviousPerformance(
- mesocycleExercise.exerciseId
- );
-
- // Create sets based on mesocycle configuration
- const sets: WorkoutSet[] = [];
- for (let i = 0; i < mesocycleExercise.targetSets; i++) {
- const previousSet =
- previousPerformance?.sets && previousPerformance.sets[i];
- sets.push(
- createEmptySet(mesocycleExercise.exerciseId, i + 1, previousSet)
- );
- }
-
- exercisesWithSets.push({
- exerciseId: mesocycleExercise.exerciseId,
- sets,
- notes: mesocycleExercise.notes,
- });
- }
-
- // Update workout with exercises
- setWorkout({
- ...newWorkout,
- exercises: exercisesWithSets,
- });
- }
}, []);
const startWorkoutFromSplit = useCallback(