Skip to main content

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.

Calendar Widget

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;
}
The function checks:
  1. Day log exists - User logged at least something
  2. Day config exists - There’s a scheduled workout for that weekday
  3. 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.
To view different months:
1

Use navigation arrows

Click the left chevron (◄) to go to previous months or right chevron (►) to go to future months.
2

View any month

Navigate as far back or forward as needed. The app maintains all historical data.
3

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:

History Header

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>
  );
})}
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.

Date Formatting

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.

Build docs developers (and LLMs) love