- Add rules_engine.py with quantitative rules for all 8 workout types - Add quality gate retry loop in generate_single_workout() - Expand calibrate_structure_rules to all 120 combinations (8 types × 5 goals × 3 sections) - Wire WeeklySplitPattern DB records into _pick_weekly_split() - Enforce movement patterns from WorkoutStructureRule in exercise selection - Add straight-set strength support (single main lift, 4-6 rounds) - Add modality consistency check for duration-dominant workout types - Add InjuryStep component to onboarding and preferences - Add sibling exercise exclusion in regenerate and preview_day endpoints - Display generator warnings on dashboard - Expand fix_rep_durations, fix_exercise_flags, fix_movement_pattern_typo - Add audit_exercise_data and check_rules_drift management commands - Add Next.js frontend with dashboard, onboarding, preferences, history pages - Add generator app with ML-powered workout generation pipeline - 96 new tests across 7 test modules Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
"use client";
|
|
|
|
import { DAY_NAMES } from "@/lib/types";
|
|
|
|
interface ScheduleStepProps {
|
|
daysPerWeek: number;
|
|
preferredDays: number[];
|
|
onChange: (data: {
|
|
days_per_week?: number;
|
|
preferred_days?: number[];
|
|
}) => void;
|
|
}
|
|
|
|
const DAYS_OPTIONS = [3, 4, 5, 6];
|
|
|
|
export function ScheduleStep({
|
|
daysPerWeek,
|
|
preferredDays,
|
|
onChange,
|
|
}: ScheduleStepProps) {
|
|
const toggleDay = (dayIndex: number) => {
|
|
if (preferredDays.includes(dayIndex)) {
|
|
onChange({
|
|
preferred_days: preferredDays.filter((d) => d !== dayIndex),
|
|
});
|
|
} else {
|
|
onChange({
|
|
preferred_days: [...preferredDays, dayIndex],
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<h2 className="text-2xl font-bold text-zinc-100 mb-2">
|
|
Your Schedule
|
|
</h2>
|
|
<p className="text-zinc-400 mb-8">
|
|
How often do you want to work out, and which days work best for you?
|
|
</p>
|
|
|
|
{/* Days per week */}
|
|
<div className="mb-8">
|
|
<h3 className="text-sm font-semibold text-zinc-400 uppercase tracking-wider mb-3">
|
|
Days Per Week
|
|
</h3>
|
|
<div className="flex gap-3">
|
|
{DAYS_OPTIONS.map((num) => {
|
|
const isSelected = daysPerWeek === num;
|
|
return (
|
|
<button
|
|
key={num}
|
|
onClick={() => onChange({ days_per_week: num })}
|
|
className={`flex-1 py-3 rounded-lg text-lg font-bold transition-all duration-150 cursor-pointer ${
|
|
isSelected
|
|
? "bg-accent text-black"
|
|
: "bg-zinc-800 text-zinc-300 hover:bg-zinc-700"
|
|
}`}
|
|
>
|
|
{num}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Preferred days */}
|
|
<div>
|
|
<h3 className="text-sm font-semibold text-zinc-400 uppercase tracking-wider mb-3">
|
|
Preferred Days
|
|
</h3>
|
|
<div className="flex gap-2">
|
|
{DAY_NAMES.map((name, index) => {
|
|
const isSelected = preferredDays.includes(index);
|
|
return (
|
|
<button
|
|
key={index}
|
|
onClick={() => toggleDay(index)}
|
|
className={`flex-1 py-3 rounded-lg text-sm font-semibold transition-all duration-150 cursor-pointer ${
|
|
isSelected
|
|
? "bg-accent text-black"
|
|
: "bg-zinc-800 text-zinc-300 hover:bg-zinc-700"
|
|
}`}
|
|
>
|
|
{name}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
<p className="text-sm text-zinc-500 mt-3">
|
|
Select the days you prefer to train. This helps us schedule rest days
|
|
optimally.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|