Files
proof-of-work/proof-of-work/app/main/workouts/new/page.tsx
T
Keysat 4be489d6d3 v1.2.0:5 — Gear (breathing, 1-5) replaces RPE as the effort field for cardio
Cardio exercises now log a breathing "Gear" (1-5, per Brian MacKenzie)
instead of RPE (6-10) as their effort field; strength keeps RPE. An exercise
counts as cardio when its equipment type is "cardio" or it carries the
"cardio" muscle group (isCardioExercise in lib/exerciseOptions), so the
Assault Bike (type "assault bike") qualifies.

New nullable SetLog.gear column added by the boot-time guarded ALTER in
docker_entrypoint.sh (additive, idempotent); plumbed through all 5 set-write
paths, the summary/edit views, and CSV/JSON import-export. Existing rpe data
is untouched and still displays. Program/AI target-RPE is unaffected.
2026-06-16 14:49:15 -05:00

107 lines
3.4 KiB
TypeScript

import { redirect } from "next/navigation";
import Link from "next/link";
import { ChevronLeft } from "lucide-react";
import { getCurrentUser } from "@/lib/auth";
import { getExercises } from "@/lib/db/exercises";
import { getWorkoutById } from "@/lib/db/workouts";
import WorkoutForm, { EditWorkoutData } from "@/components/workouts/WorkoutForm";
export const metadata = {
title: "Log Workout",
description: "Log a new workout",
};
export default async function NewWorkoutPage(props: {
searchParams: Promise<{ edit?: string }>;
}) {
const searchParams = await props.searchParams;
const user = await getCurrentUser();
if (!user) {
redirect("/auth/login");
}
const exercises = await getExercises(user.id);
// If ?edit=WORKOUT_ID, fetch existing workout for editing
let editWorkout: EditWorkoutData | undefined;
if (searchParams.edit) {
const workout = await getWorkoutById(searchParams.edit);
if (workout && workout.userId === user.id) {
// Group sets by exercise
const grouped: Record<string, EditWorkoutData["exercises"][number]> = {};
for (const set of workout.setLogs) {
const exId = set.exercise.id;
let customMetrics: Record<string, string> | undefined;
if ((set as any).customMetrics) {
try {
customMetrics = JSON.parse((set as any).customMetrics);
} catch {
customMetrics = undefined;
}
}
if (!grouped[exId]) {
grouped[exId] = {
exercise: set.exercise,
sets: [],
};
}
grouped[exId].sets.push({
setNumber: set.setNumber,
reps: set.reps ?? undefined,
weight: set.weight ?? undefined,
rpe: set.rpe ?? undefined,
gear: set.gear ?? undefined,
durationSeconds: set.durationSeconds ?? undefined,
distance: set.distance ?? undefined,
calories: set.calories ?? undefined,
watts: set.watts ?? undefined,
customMetrics,
notes: set.notes ?? undefined,
});
}
editWorkout = {
id: workout.id,
name: workout.name || "",
date: workout.date.toISOString(),
durationMinutes: workout.durationMinutes,
difficulty: workout.difficulty,
caloriesBurned: (workout as any).caloriesBurned ?? null,
notes: workout.notes,
exercises: Object.values(grouped),
};
}
}
const isEditing = !!editWorkout;
return (
<div className="min-h-screen bg-[#0A0A0A] pb-24 md:pb-8">
{/* Header */}
<div className="border-b border-zinc-800 sticky top-0 z-40 bg-[#0A0A0A]">
<div className="max-w-2xl mx-auto px-4 py-4 flex items-center gap-4">
<Link
href={isEditing ? `/main/workouts/${editWorkout!.id}` : "/main/workouts"}
className="p-2 hover:bg-zinc-900 rounded-lg -ml-2 text-zinc-400 hover:text-white"
aria-label="Back"
>
<ChevronLeft className="w-6 h-6" />
</Link>
<h1 className="text-2xl font-display text-white tracking-wider">
{isEditing ? "Edit Workout" : "Log Workout"}
</h1>
</div>
</div>
{/* Form */}
<div className="max-w-2xl mx-auto px-4 py-6 pb-12">
<WorkoutForm
exercises={exercises}
recentlyUsedExercises={[]}
editWorkout={editWorkout}
/>
</div>
</div>
);
}