Overview
The Week tab provides a comprehensive calendar view and workout history tracker. It visualizes your completed workouts, displays your weekly schedule, and maintains a detailed log of past sessions with full rep and set information.
Calendar Widget Monthly view with completion indicators for each day.
Weekly Schedule See your planned workout routine for each day of the week.
Workout History Detailed logs of past workouts with sets and reps.
Navigation Controls Browse through months to view historical data.
The calendar provides a visual overview of your workout consistency:
Visual Indicators
Green gradient cells - Days where you completed all scheduled exercises
White/gray cells - Incomplete or rest days
Checkmark icon - Appears on completed days
Current month - Displayed at the top with navigation arrows
A day is marked as “completed” only when all exercises in that day’s workout are logged. Partial workouts don’t count toward completion.
Completion Logic
From WeekTab.jsx:37-50:
function isWorkoutComplete ( dayLog , workoutData , weekdayName ) {
if ( ! dayLog || Object . keys ( dayLog ). length === 0 ) return false ;
const dayConfig = workoutData . find (( day ) => day . dayOfWeek === weekdayName );
if ( ! dayConfig ) return false ;
const loggedExercises = Object . values ( dayLog || {}). filter (
( logs ) => Array . isArray ( logs ) && logs . length > 0
);
if ( loggedExercises . length === 0 ) return false ;
return loggedExercises . length === dayConfig . exercises . length ;
}
Why this completion logic?
The function checks:
Day log exists - User logged at least something
Day config exists - There’s a scheduled workout for that weekday
All exercises logged - Number of logged exercises matches the day’s total exercises
This ensures partial workouts don’t count as “complete” for streak purposes.
Navigating Months
To view different months:
Use navigation arrows
Click the left chevron (◄) to go to previous months or right chevron (►) to go to future months.
View any month
Navigate as far back or forward as needed. The app maintains all historical data.
Return to current month
No “today” button exists, but you can navigate back to the current month using the arrows.
From WeekTab.jsx:73-91:
const previousMonth = () => {
setDisplayMonth (( prev ) => {
if ( prev === 0 ) {
setDisplayYear (( year ) => year - 1 );
return 11 ;
}
return prev - 1 ;
});
};
const nextMonth = () => {
setDisplayMonth (( prev ) => {
if ( prev === 11 ) {
setDisplayYear (( year ) => year + 1 );
return 0 ;
}
return prev + 1 ;
});
};
Calendar Implementation
The calendar grid is generated dynamically:
From WeekTab.jsx:93-112:
const calendarDays = useMemo (() => {
const firstDay = new Date ( displayYear , displayMonth , 1 );
const lastDay = new Date ( displayYear , displayMonth + 1 , 0 );
const days = [];
// Add empty cells for days before the first day of month
for ( let i = 0 ; i < firstDay . getDay (); i ++ ) {
days . push ( null );
}
// Add all days of the month
for ( let i = 1 ; i <= lastDay . getDate (); i ++ ) {
days . push ( new Date ( displayYear , displayMonth , i ));
}
return {
days ,
monthYear: firstDay . toLocaleDateString ( "en-US" , { month: "long" , year: "numeric" })
};
}, [ displayMonth , displayYear ]);
The calendar uses useMemo to avoid recalculating days on every render, improving performance.
Weekly Schedule
Below the calendar, you’ll find your complete weekly workout plan:
Schedule Display
Day cards - One card per day showing:
Day of the week (e.g., “Monday”)
Workout focus (e.g., “Push Day - Chest, Shoulders, Triceps”)
List of all exercises
Sets and target reps for each exercise
Exercise Cards
From WeekTab.jsx:212-234:
{ workoutData . map (( day ) => (
< Card key = { day . id } >
< CardHeader >
< CardTitle className = "text-slate-900 dark:text-zinc-100" > { day . dayOfWeek } </ CardTitle >
< CardDescription className = "text-slate-600 dark:text-zinc-300" > { day . focus } </ CardDescription >
</ CardHeader >
< CardContent >
< ul className = "space-y-2" >
{ day . exercises . map (( exercise ) => (
< li
key = { exercise . id }
className = "rounded-xl border border-slate-200 bg-gradient-to-r from-white to-cyan-50/50 p-3 text-sm"
>
< p className = "font-medium text-slate-900 dark:text-zinc-100" > { exercise . name } </ p >
< p className = "text-slate-600 dark:text-zinc-300" >
{ exercise . sets } sets - { exercise . targetReps }
</ p >
</ li >
)) }
</ ul >
</ CardContent >
</ Card >
))}
Use the weekly schedule to plan your week ahead and see what exercises are coming up.
Workout History
The history section shows detailed logs of all past workouts:
From WeekTab.jsx:236-246:
< Card className = "border-0 bg-gradient-to-br from-amber-400 via-orange-500 to-rose-500 text-white shadow-[0_18px_40px_-20px_rgba(251,146,60,0.85)]" >
< CardHeader >
< CardTitle className = "flex items-center gap-2" >
< History className = "h-4 w-4 text-orange-50" />
Workout History
</ CardTitle >
< CardDescription className = "text-orange-50/95" >
Past logs saved in localStorage. Most recent dates are shown first.
</ CardDescription >
</ CardHeader >
</ Card >
History Entries
Each history entry displays:
Date - Full date with weekday (e.g., “Fri, Mar 7, 2026”)
Workout focus - The workout type for that day
Summary stats :
Total sets logged
Total reps completed
Exercise-by-exercise breakdown :
Exercise name
Number of sets
Reps for each set
History Implementation
From WeekTab.jsx:255-304:
{ historyEntries . map (([ dateKey , dayLog ]) => {
const weekdayName = getWeekdayName ( dateKey );
const dayConfig = workoutData . find (( day ) => day . dayOfWeek === weekdayName );
const loggedExercises = Object . entries ( dayLog || {}). filter (([, logs ]) =>
Array . isArray ( logs ) && logs . length > 0
);
const totalSets = loggedExercises . reduce (( sum , [, logs ]) => sum + logs . length , 0 );
const totalReps = loggedExercises . reduce (( sum , [, logs ]) => {
return (
sum +
logs . reduce (( repSum , logEntry ) => {
return repSum + getNormalizedReps ( logEntry );
}, 0 )
);
}, 0 );
return (
< Card key = { dateKey } >
< CardHeader >
< CardTitle className = "text-slate-900 dark:text-zinc-100" > { getDisplayDate ( dateKey ) } </ CardTitle >
< CardDescription className = "text-slate-600 dark:text-zinc-300" >
{ weekdayName }
{ dayConfig ? ` - ${ dayConfig . focus } ` : "" }
</ CardDescription >
</ CardHeader >
< CardContent className = "space-y-2" >
< p className = "text-sm font-medium text-cyan-700 dark:text-cyan-300" >
Logged sets: { totalSets } | Total reps: { totalReps }
</ p >
< ul className = "space-y-2" >
{ loggedExercises . map (([ exerciseId , logs ]) => {
const exerciseName = exerciseLookup [ exerciseId ]?. name || exerciseId ;
const repsList = logs . map (( logEntry ) => getNormalizedReps ( logEntry )). join ( ", " );
return (
< li
key = { ` ${ dateKey } - ${ exerciseId } ` }
className = "rounded-xl border border-slate-200 bg-slate-50/85 p-3 text-sm"
>
< p className = "font-medium text-slate-900 dark:text-zinc-100" > { exerciseName } </ p >
< p className = "text-slate-600 dark:text-zinc-300" >
{ logs . length } set(s) - Reps: { repsList }
</ p >
</ li >
);
}) }
</ ul >
</ CardContent >
</ Card >
);
})}
Understanding getNormalizedReps()
function getNormalizedReps ( logEntry ) {
if ( typeof logEntry === "number" ) {
return logEntry ;
}
return Number ( logEntry ?. reps || 0 );
}
This function handles legacy data formats where reps might be stored as plain numbers instead of objects with a reps property. It ensures backward compatibility.
Empty State
If no workouts have been logged:
{ historyEntries . length === 0 ? (
< Card >
< CardContent className = "pt-4" >
< p className = "text-sm text-slate-600 dark:text-zinc-300" >
No past workout logs yet. Start logging sets in Today.
</ p >
</ CardContent >
</ Card >
) : (
// History entries...
)}
Data Processing
Exercise Lookup
The app creates an exercise lookup table for fast name resolution:
From WeekTab.jsx:57-65:
const exerciseLookup = useMemo (() => {
const lookup = {};
workoutData . forEach (( day ) => {
day . exercises . forEach (( exercise ) => {
lookup [ exercise . id ] = exercise ;
});
});
return lookup ;
}, [ workoutData ]);
History Sorting
Entries are sorted with most recent dates first:
From WeekTab.jsx:67-71:
const historyEntries = useMemo (() => {
return Object . entries ( completedExercises || {})
. filter (([, dayLog ]) => dayLog && Object . keys ( dayLog ). length > 0 )
. sort (([ dateA ], [ dateB ]) => dateB . localeCompare ( dateA ));
}, [ completedExercises ]);
Using localeCompare() on ISO date strings (YYYY-MM-DD) provides correct chronological sorting.
The app uses multiple date format functions:
// Full display: "Fri, Mar 7, 2026"
function getDisplayDate ( dateKey ) {
return new Intl . DateTimeFormat ( "en-US" , {
weekday: "short" ,
month: "short" ,
day: "numeric" ,
year: "numeric" ,
}). format ( getDateObject ( dateKey ));
}
// Weekday only: "Friday"
function getWeekdayName ( dateKey ) {
return new Intl . DateTimeFormat ( "en-US" , { weekday: "long" })
. format ( getDateObject ( dateKey ));
}
// Storage key: "2026-03-07"
function getDayKey ( date ) {
const year = date . getFullYear ();
const month = String ( date . getMonth () + 1 ). padStart ( 2 , "0" );
const day = String ( date . getDate ()). padStart ( 2 , "0" );
return ` ${ year } - ${ month } - ${ day } ` ;
}
Dark Mode Styling
The Week tab features comprehensive dark mode support:
Calendar cells - Zinc backgrounds in dark mode
Completion gradient - Maintains emerald-green vibrancy
Schedule cards - Dark zinc tones with proper contrast
History entries - Readable text on dark backgrounds
Navigation buttons - Zinc borders and hover states
All gradients and colors are carefully chosen to maintain readability and visual hierarchy in both light and dark modes.