designer / src /training_program_designer.tsx
vshankar97's picture
Rename src/training_program_designer.jsx to src/training_program_designer.tsx
08ab7b1 verified
Raw
History Blame Contribute Delete
39.2 kB
import React, { useMemo, useState } from "react";
import { motion } from "framer-motion";
import {
AlertCircle,
CheckCircle2,
Dumbbell,
FileDown,
ShieldAlert,
HeartPulse,
} from "lucide-react";
type EquipmentKey = "gym" | "bands" | "freeWeights" | "bodyWeight";
type FmsKey =
| "deepSquat"
| "hurdleStep"
| "inlineLunge"
| "shoulderMobility"
| "activeStraightLegRaise"
| "trunkStabilityPushUp"
| "rotaryStability";
type ContraKey =
| "highBloodPressure"
| "cardiacHistory"
| "lowBackPain"
| "shoulderPain"
| "kneePain"
| "anklePain"
| "pregnant"
| "postSurgery"
| "otherMedical";
type JointKey =
| "footAnkle"
| "knee"
| "hip"
| "lumbar"
| "thoracic"
| "cervical"
| "shoulder"
| "elbowWrist";
type ResistanceGoal = "hypertrophy" | "strength" | "endurance" | "fat_loss" | "general_fitness";
type CvFitnessLevel = "low" | "below_average" | "average" | "good" | "excellent";
type CardioPreference = "walking" | "cycling" | "rowing" | "running" | "mixed";
type Intake = {
clientName: string;
trainerName: string;
age: string;
sex: string;
weightKg: string;
heightCm: string;
bodyFat: string;
restingHeartRate: string;
systolicBP: string;
diastolicBP: string;
goal: string;
resistanceGoal: ResistanceGoal;
trainingAge: string;
frequencyPerWeek: string;
sessionDurationMin: string;
programLengthWeeks: string;
cvFitnessLevel: CvFitnessLevel;
cardioPreference: CardioPreference;
healthHistory: string;
currentMedications: string;
injuryNotes: string;
equipment: Record<EquipmentKey, boolean>;
contraindications: Record<ContraKey, boolean>;
kineticChain: Record<JointKey, string>;
fms: Record<FmsKey, number>;
clearingTests: {
shoulderPainWithClearing: boolean;
extensionPainWithClearing: boolean;
flexionPainWithClearing: boolean;
rotaryPainWithClearing: boolean;
};
};
type SubmissionPayload = {
intake: Intake;
rules: {
training_3_days_or_less_is_total_body: boolean;
include_all_7_patterns: boolean;
include_primary_and_assistance_exercises: boolean;
include_resistance_training: boolean;
include_cardiovascular_training: boolean;
include_flexibility_training: boolean;
pdf_only_output: boolean;
output_packet_includes_trainer_reference_and_client_handout: boolean;
trainer_reference_includes_progression_and_deload: boolean;
client_handout_must_be_simple_and_trackable: boolean;
resistance_training_must_include_sets_reps_intensity_sets_per_week_tempo_and_rest: boolean;
conditioning_must_consider_cv_fitness_level: boolean;
collect_rhr_and_blood_pressure: boolean;
deload_every_6_to_8_weeks: boolean;
progress_supported_to_integrated_then_more_closed_chain_when_tolerated: boolean;
};
outputTemplate: {
packetTitle: string;
sections: string[];
trainerReferenceFields: string[];
clientHandoutFields: string[];
resistancePrescriptionColumns: string[];
cardioFields: string[];
flexibilityFields: string[];
};
};
const APP_CONFIG = {
appName: "Program Designer",
apiBaseUrl: "/api/generate-program-pdf",
pdfFilenamePrefix: "training-program-packet",
};
const equipmentLabels: Record<EquipmentKey, string> = {
gym: "Gym Machines",
bands: "Resistance Bands",
freeWeights: "Free Weights",
bodyWeight: "Body Weight",
};
const contraLabels: Record<ContraKey, string> = {
highBloodPressure: "High Blood Pressure",
cardiacHistory: "Cardiac History",
lowBackPain: "Low Back Pain",
shoulderPain: "Shoulder Pain",
kneePain: "Knee Pain",
anklePain: "Ankle Pain",
pregnant: "Pregnancy",
postSurgery: "Post Surgery",
otherMedical: "Other Medical Concern",
};
const jointLabels: Record<JointKey, string> = {
footAnkle: "Foot / Ankle",
knee: "Knee",
hip: "Hip",
lumbar: "Lumbar Spine",
thoracic: "Thoracic Spine",
cervical: "Cervical Spine",
shoulder: "Shoulder",
elbowWrist: "Elbow / Wrist",
};
const resistanceGoalLabels: Record<ResistanceGoal, string> = {
hypertrophy: "Hypertrophy",
strength: "Strength",
endurance: "Muscular Endurance",
fat_loss: "Fat Loss / General Conditioning",
general_fitness: "General Fitness",
};
const cvFitnessLabels: Record<CvFitnessLevel, string> = {
low: "Low",
below_average: "Below Average",
average: "Average",
good: "Good",
excellent: "Excellent",
};
const cardioPreferenceLabels: Record<CardioPreference, string> = {
walking: "Walking",
cycling: "Cycling",
rowing: "Rowing",
running: "Running",
mixed: "Mixed Modes",
};
const fmsFields: { key: FmsKey; label: string }[] = [
{ key: "deepSquat", label: "Deep Squat" },
{ key: "hurdleStep", label: "Hurdle Step" },
{ key: "inlineLunge", label: "Inline Lunge" },
{ key: "shoulderMobility", label: "Shoulder Mobility" },
{ key: "activeStraightLegRaise", label: "ASLR" },
{ key: "trunkStabilityPushUp", label: "Trunk Stability Push-Up" },
{ key: "rotaryStability", label: "Rotary Stability" },
];
const defaultIntake: Intake = {
clientName: "",
trainerName: "",
age: "",
sex: "",
weightKg: "",
heightCm: "",
bodyFat: "",
restingHeartRate: "",
systolicBP: "",
diastolicBP: "",
goal: "General fitness and movement quality",
resistanceGoal: "hypertrophy",
trainingAge: "beginner",
frequencyPerWeek: "3",
sessionDurationMin: "60",
programLengthWeeks: "6",
cvFitnessLevel: "average",
cardioPreference: "walking",
healthHistory: "",
currentMedications: "",
injuryNotes: "",
equipment: {
gym: true,
bands: true,
freeWeights: true,
bodyWeight: true,
},
contraindications: {
highBloodPressure: false,
cardiacHistory: false,
lowBackPain: false,
shoulderPain: false,
kneePain: false,
anklePain: false,
pregnant: false,
postSurgery: false,
otherMedical: false,
},
kineticChain: {
footAnkle: "",
knee: "",
hip: "",
lumbar: "",
thoracic: "",
cervical: "",
shoulder: "",
elbowWrist: "",
},
fms: {
deepSquat: 1,
hurdleStep: 1,
inlineLunge: 1,
shoulderMobility: 1,
activeStraightLegRaise: 1,
trunkStabilityPushUp: 1,
rotaryStability: 1,
},
clearingTests: {
shoulderPainWithClearing: false,
extensionPainWithClearing: false,
flexionPainWithClearing: false,
rotaryPainWithClearing: false,
},
};
const styles: Record<string, React.CSSProperties> = {
page: {
minHeight: "100vh",
background: "#f8fafc",
padding: "24px",
fontFamily: "Arial, sans-serif",
color: "#111827",
},
container: {
maxWidth: "1100px",
margin: "0 auto",
},
card: {
background: "#ffffff",
border: "1px solid #e5e7eb",
borderRadius: "20px",
boxShadow: "0 2px 8px rgba(0,0,0,0.04)",
overflow: "hidden",
},
header: {
padding: "24px",
borderBottom: "1px solid #e5e7eb",
},
content: {
padding: "24px",
},
hero: {
display: "flex",
gap: "16px",
alignItems: "flex-start",
marginBottom: "20px",
},
iconBox: {
border: "1px solid #e5e7eb",
borderRadius: "16px",
padding: "12px",
display: "inline-flex",
background: "#fff",
},
title: {
margin: 0,
fontSize: "28px",
fontWeight: 700,
},
subtitle: {
margin: "6px 0 0 0",
color: "#6b7280",
fontSize: "14px",
},
metricGrid: {
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))",
gap: "12px",
marginTop: "20px",
},
metricCard: {
border: "1px solid #e5e7eb",
borderRadius: "16px",
padding: "16px",
background: "#fff",
},
metricLabel: {
fontSize: "11px",
textTransform: "uppercase",
letterSpacing: "0.05em",
color: "#6b7280",
},
metricValue: {
marginTop: "6px",
fontSize: "22px",
fontWeight: 700,
},
section: {
marginBottom: "32px",
},
sectionTitle: {
margin: 0,
fontSize: "20px",
fontWeight: 700,
},
sectionDescription: {
margin: "6px 0 0 0",
fontSize: "14px",
color: "#6b7280",
},
grid4: {
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
gap: "16px",
marginTop: "16px",
},
grid3: {
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
gap: "16px",
marginTop: "16px",
},
field: {
display: "flex",
flexDirection: "column",
gap: "8px",
},
label: {
fontSize: "14px",
fontWeight: 600,
},
input: {
width: "100%",
boxSizing: "border-box",
border: "1px solid #d1d5db",
borderRadius: "12px",
padding: "12px 14px",
fontSize: "14px",
background: "#fff",
},
textarea: {
width: "100%",
boxSizing: "border-box",
border: "1px solid #d1d5db",
borderRadius: "12px",
padding: "12px 14px",
fontSize: "14px",
minHeight: "120px",
resize: "vertical",
background: "#fff",
},
checkboxGrid: {
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
gap: "12px",
marginTop: "16px",
},
checkboxCard: {
display: "flex",
gap: "10px",
alignItems: "flex-start",
border: "1px solid #e5e7eb",
borderRadius: "16px",
padding: "14px",
background: "#fff",
},
divider: {
border: 0,
borderTop: "1px solid #e5e7eb",
margin: "28px 0",
},
statusBox: {
borderRadius: "16px",
padding: "14px 16px",
marginTop: "18px",
fontSize: "14px",
},
actionBox: {
border: "1px solid #e5e7eb",
borderRadius: "16px",
padding: "18px",
background: "#fff",
},
actionRow: {
display: "flex",
gap: "16px",
alignItems: "center",
justifyContent: "space-between",
flexWrap: "wrap",
},
button: {
border: 0,
borderRadius: "14px",
padding: "12px 18px",
background: "#111827",
color: "#fff",
fontSize: "14px",
fontWeight: 700,
cursor: "pointer",
display: "inline-flex",
alignItems: "center",
gap: "8px",
},
buttonDisabled: {
opacity: 0.6,
cursor: "not-allowed",
},
cautionBox: {
border: "1px solid #fcd34d",
background: "#fffbeb",
color: "#92400e",
borderRadius: "16px",
padding: "16px",
marginTop: "18px",
},
infoBox: {
border: "1px solid #dbeafe",
background: "#eff6ff",
color: "#1e3a8a",
borderRadius: "16px",
padding: "16px",
marginTop: "18px",
},
smallText: {
fontSize: "14px",
color: "#6b7280",
margin: "6px 0 0 0",
},
tablePreview: {
width: "100%",
borderCollapse: "collapse",
marginTop: "12px",
fontSize: "13px",
},
th: {
background: "#f59e0b",
color: "#fff",
textAlign: "left",
padding: "10px",
border: "1px solid #d1d5db",
},
td: {
padding: "10px",
border: "1px solid #d1d5db",
background: "#fafaf9",
verticalAlign: "top",
},
};
function num(value: string, fallback = 0) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : fallback;
}
function fmsTotal(intake: Intake) {
return Object.values(intake.fms).reduce((sum, value) => sum + value, 0);
}
function bmi(intake: Intake) {
const w = num(intake.weightKg);
const h = num(intake.heightCm) / 100;
if (!w || !h) return null;
return +(w / (h * h)).toFixed(1);
}
function usesTotalBodyRule(intake: Intake) {
return num(intake.frequencyPerWeek, 3) <= 3;
}
function resistanceSummary(goal: ResistanceGoal) {
switch (goal) {
case "strength":
return "Backend should bias lower rep ranges, longer rest, and higher relative loading for primary lifts.";
case "endurance":
return "Backend should bias higher rep ranges, lower loads, shorter rest, and higher local muscular fatigue tolerance.";
case "hypertrophy":
return "Backend should bias moderate rep ranges, moderate rest, and sufficient weekly volume.";
case "fat_loss":
return "Backend should bias moderate density, total body patterns, and supporting conditioning work.";
default:
return "Backend should bias general fitness dosing with broad movement coverage and manageable fatigue.";
}
}
function cardioSummary(level: CvFitnessLevel) {
switch (level) {
case "low":
return "Conditioning should begin with lower-intensity intervals or steady work and conservative progression.";
case "below_average":
return "Conditioning should progress gradually with moderate introductory volume.";
case "average":
return "Conditioning can use standard moderate-intensity prescriptions and measured progression.";
case "good":
return "Conditioning may include longer intervals or greater weekly volume if tolerated.";
case "excellent":
return "Conditioning can tolerate higher workload or more advanced interval structure if goals require it.";
}
}
function riskMessages(intake: Intake) {
const flags: string[] = [];
if (intake.clearingTests.shoulderPainWithClearing) flags.push("Shoulder clearing test pain recorded.");
if (intake.clearingTests.extensionPainWithClearing) flags.push("Extension clearing test pain recorded.");
if (intake.clearingTests.flexionPainWithClearing) flags.push("Flexion clearing test pain recorded.");
if (intake.clearingTests.rotaryPainWithClearing) flags.push("Rotary clearing test pain recorded.");
if (intake.contraindications.highBloodPressure) flags.push("High blood pressure flagged.");
if (intake.contraindications.cardiacHistory) flags.push("Cardiac history flagged.");
if (intake.contraindications.postSurgery) flags.push("Post-surgical history flagged.");
return flags;
}
function buildPayload(intake: Intake): SubmissionPayload {
return {
intake,
rules: {
training_3_days_or_less_is_total_body: true,
include_all_7_patterns: true,
include_primary_and_assistance_exercises: true,
include_resistance_training: true,
include_cardiovascular_training: true,
include_flexibility_training: true,
pdf_only_output: true,
output_packet_includes_trainer_reference_and_client_handout: true,
trainer_reference_includes_progression_and_deload: true,
client_handout_must_be_simple_and_trackable: true,
resistance_training_must_include_sets_reps_intensity_sets_per_week_tempo_and_rest: true,
conditioning_must_consider_cv_fitness_level: true,
collect_rhr_and_blood_pressure: true,
deload_every_6_to_8_weeks: true,
progress_supported_to_integrated_then_more_closed_chain_when_tolerated: true,
},
outputTemplate: {
packetTitle: "Trainer Reference + Client Handout Packet",
sections: [
"Program Summary",
"Trainer Reference Sheet",
"Client Handout",
],
trainerReferenceFields: [
"program length",
"training frequency",
"split type",
"resting heart rate",
"blood pressure",
"warm-up",
"resistance training goal",
"resistance training prescription",
"cardiovascular prescription",
"flexibility prescription",
"intensity",
"repetitions",
"sets per week",
"tempo",
"rest intervals",
"progression notes",
"deload week",
"substitutions",
],
clientHandoutFields: [
"day label",
"exercise",
"intensity",
"repetitions",
"sets per week",
"tempo",
"rest interval",
"load used",
"cardio",
"flexibility",
"completion tracking boxes",
"notes",
],
resistancePrescriptionColumns: [
"Exercises",
"Intensity",
"Repetitions",
"Sets/Week",
"Tempo (C:I:E)",
"Rest Interval Between Sets",
],
cardioFields: [
"mode",
"frequency",
"duration",
"intensity / zone / RPE",
"progression",
],
flexibilityFields: [
"exercise",
"duration or reps",
"frequency",
],
},
};
}
async function requestProgramPdf(payload: SubmissionPayload) {
const response = await fetch(APP_CONFIG.apiBaseUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
throw new Error("PDF generation failed");
}
const blob = await response.blob();
const contentType = response.headers.get("content-type") || "";
if (!contentType.includes("pdf")) {
throw new Error("Server did not return a PDF file");
}
return blob;
}
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
URL.revokeObjectURL(url);
}
function buildFilename(intake: Intake) {
const safeName = (intake.clientName || "client")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
return `${APP_CONFIG.pdfFilenamePrefix}-${safeName || "client"}.pdf`;
}
function FieldLabel({ children }: { children: React.ReactNode }) {
return <label style={styles.label}>{children}</label>;
}
function SectionHeader({ title, description }: { title: string; description: string }) {
return (
<div>
<h3 style={styles.sectionTitle}>{title}</h3>
<p style={styles.sectionDescription}>{description}</p>
</div>
);
}
export default function TrainingProgramDesignerEmbed() {
const [intake, setIntake] = useState<Intake>(defaultIntake);
const [isSubmitting, setIsSubmitting] = useState(false);
const [status, setStatus] = useState<"idle" | "success" | "error">("idle");
const [message, setMessage] = useState("");
const currentBmi = useMemo(() => bmi(intake), [intake]);
const currentFmsTotal = useMemo(() => fmsTotal(intake), [intake]);
const currentRiskMessages = useMemo(() => riskMessages(intake), [intake]);
const totalBodyRuleActive = useMemo(() => usesTotalBodyRule(intake), [intake]);
function setField<K extends keyof Intake>(key: K, value: Intake[K]) {
setIntake((prev) => ({ ...prev, [key]: value }));
}
function setEquipment(key: EquipmentKey, value: boolean) {
setIntake((prev) => ({ ...prev, equipment: { ...prev.equipment, [key]: value } }));
}
function setContra(key: ContraKey, value: boolean) {
setIntake((prev) => ({ ...prev, contraindications: { ...prev.contraindications, [key]: value } }));
}
function setFms(key: FmsKey, value: number) {
setIntake((prev) => ({ ...prev, fms: { ...prev.fms, [key]: value } }));
}
function setJoint(key: JointKey, value: string) {
setIntake((prev) => ({ ...prev, kineticChain: { ...prev.kineticChain, [key]: value } }));
}
function setClearing<K extends keyof Intake["clearingTests"]>(key: K, value: boolean) {
setIntake((prev) => ({ ...prev, clearingTests: { ...prev.clearingTests, [key]: value } }));
}
async function handleGenerate() {
setIsSubmitting(true);
setStatus("idle");
setMessage("");
try {
const payload = buildPayload(intake);
const pdfBlob = await requestProgramPdf(payload);
downloadBlob(pdfBlob, buildFilename(intake));
setStatus("success");
setMessage("Your trainer reference and client handout PDF packet has been generated.");
} catch (error) {
setStatus("error");
setMessage(error instanceof Error ? error.message : "Unable to generate the PDF packet.");
} finally {
setIsSubmitting(false);
}
}
return (
<div style={styles.page}>
<div style={styles.container}>
<motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }}>
<div style={styles.card}>
<div style={styles.header}>
<div style={styles.hero}>
<div style={styles.iconBox}>
<Dumbbell size={24} />
</div>
<div>
<h1 style={styles.title}>{APP_CONFIG.appName}</h1>
<p style={styles.subtitle}>
Generate a printable packet that includes a trainer reference sheet and a client handout with resistance, conditioning, and flexibility programming.
</p>
</div>
</div>
<div style={styles.metricGrid}>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>BMI</div>
<div style={styles.metricValue}>{currentBmi ?? "—"}</div>
</div>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>FMS Total</div>
<div style={styles.metricValue}>{currentFmsTotal}</div>
</div>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>Resting HR</div>
<div style={styles.metricValue}>{intake.restingHeartRate || "—"}</div>
</div>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>Blood Pressure</div>
<div style={{ ...styles.metricValue, fontSize: "18px" }}>
{intake.systolicBP && intake.diastolicBP ? `${intake.systolicBP}/${intake.diastolicBP}` : "—"}
</div>
</div>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>Resistance Goal</div>
<div style={{ ...styles.metricValue, fontSize: "16px" }}>{resistanceGoalLabels[intake.resistanceGoal]}</div>
</div>
<div style={styles.metricCard}>
<div style={styles.metricLabel}>CV Fitness Level</div>
<div style={{ ...styles.metricValue, fontSize: "16px" }}>{cvFitnessLabels[intake.cvFitnessLevel]}</div>
</div>
</div>
</div>
<div style={styles.content}>
<div style={styles.section}>
<SectionHeader
title="Client Details"
description="Basic client information, resistance-training emphasis, and cardiovascular baseline data."
/>
<div style={styles.grid4}>
<div style={styles.field}>
<FieldLabel>Client Name</FieldLabel>
<input style={styles.input} value={intake.clientName} onChange={(e) => setField("clientName", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Trainer Name</FieldLabel>
<input style={styles.input} value={intake.trainerName} onChange={(e) => setField("trainerName", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Age</FieldLabel>
<input style={styles.input} value={intake.age} onChange={(e) => setField("age", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Sex</FieldLabel>
<input style={styles.input} value={intake.sex} onChange={(e) => setField("sex", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Weight (kg)</FieldLabel>
<input style={styles.input} value={intake.weightKg} onChange={(e) => setField("weightKg", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Height (cm)</FieldLabel>
<input style={styles.input} value={intake.heightCm} onChange={(e) => setField("heightCm", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Body Fat %</FieldLabel>
<input style={styles.input} value={intake.bodyFat} onChange={(e) => setField("bodyFat", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Training Age</FieldLabel>
<select style={styles.input} value={intake.trainingAge} onChange={(e) => setField("trainingAge", e.target.value)}>
<option value="beginner">Beginner</option>
<option value="intermediate">Intermediate</option>
<option value="advanced">Advanced</option>
</select>
</div>
<div style={styles.field}>
<FieldLabel>Resting Heart Rate</FieldLabel>
<input style={styles.input} value={intake.restingHeartRate} onChange={(e) => setField("restingHeartRate", e.target.value)} placeholder="bpm" />
</div>
<div style={styles.field}>
<FieldLabel>Systolic BP</FieldLabel>
<input style={styles.input} value={intake.systolicBP} onChange={(e) => setField("systolicBP", e.target.value)} placeholder="mmHg" />
</div>
<div style={styles.field}>
<FieldLabel>Diastolic BP</FieldLabel>
<input style={styles.input} value={intake.diastolicBP} onChange={(e) => setField("diastolicBP", e.target.value)} placeholder="mmHg" />
</div>
<div style={styles.field}>
<FieldLabel>Program Length (weeks)</FieldLabel>
<input style={styles.input} value={intake.programLengthWeeks} onChange={(e) => setField("programLengthWeeks", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Training Days / Week</FieldLabel>
<input style={styles.input} value={intake.frequencyPerWeek} onChange={(e) => setField("frequencyPerWeek", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Session Duration (min)</FieldLabel>
<input style={styles.input} value={intake.sessionDurationMin} onChange={(e) => setField("sessionDurationMin", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Resistance Training Goal</FieldLabel>
<select style={styles.input} value={intake.resistanceGoal} onChange={(e) => setField("resistanceGoal", e.target.value as ResistanceGoal)}>
{(Object.keys(resistanceGoalLabels) as ResistanceGoal[]).map((key) => (
<option key={key} value={key}>{resistanceGoalLabels[key]}</option>
))}
</select>
</div>
<div style={styles.field}>
<FieldLabel>CV Fitness Level</FieldLabel>
<select style={styles.input} value={intake.cvFitnessLevel} onChange={(e) => setField("cvFitnessLevel", e.target.value as CvFitnessLevel)}>
{(Object.keys(cvFitnessLabels) as CvFitnessLevel[]).map((key) => (
<option key={key} value={key}>{cvFitnessLabels[key]}</option>
))}
</select>
</div>
<div style={styles.field}>
<FieldLabel>Preferred Conditioning Mode</FieldLabel>
<select style={styles.input} value={intake.cardioPreference} onChange={(e) => setField("cardioPreference", e.target.value as CardioPreference)}>
{(Object.keys(cardioPreferenceLabels) as CardioPreference[]).map((key) => (
<option key={key} value={key}>{cardioPreferenceLabels[key]}</option>
))}
</select>
</div>
<div style={{ ...styles.field, gridColumn: "span 2" }}>
<FieldLabel>Primary Goal</FieldLabel>
<input style={styles.input} value={intake.goal} onChange={(e) => setField("goal", e.target.value)} />
</div>
</div>
</div>
<hr style={styles.divider} />
<div style={styles.section}>
<SectionHeader
title="Health and Injury History"
description="Use these notes to account for medical history, symptoms, and local tissue concerns."
/>
<div style={styles.grid3}>
<div style={styles.field}>
<FieldLabel>Health History</FieldLabel>
<textarea style={styles.textarea} value={intake.healthHistory} onChange={(e) => setField("healthHistory", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>Current Medications</FieldLabel>
<textarea style={styles.textarea} value={intake.currentMedications} onChange={(e) => setField("currentMedications", e.target.value)} />
</div>
<div style={styles.field}>
<FieldLabel>General Injury Notes</FieldLabel>
<textarea style={styles.textarea} value={intake.injuryNotes} onChange={(e) => setField("injuryNotes", e.target.value)} />
</div>
</div>
</div>
<hr style={styles.divider} />
<div style={styles.section}>
<SectionHeader
title="Available Equipment"
description="Only selected equipment should be used in the final packet."
/>
<div style={styles.checkboxGrid}>
{(Object.keys(equipmentLabels) as EquipmentKey[]).map((key) => (
<label key={key} style={styles.checkboxCard}>
<input type="checkbox" checked={intake.equipment[key]} onChange={(e) => setEquipment(key, e.target.checked)} />
<span>{equipmentLabels[key]}</span>
</label>
))}
</div>
</div>
<hr style={styles.divider} />
<div style={styles.section}>
<SectionHeader
title="FMS and Clearing Tests"
description="Movement quality and pain findings should constrain exercise selection and progression."
/>
<div style={styles.grid4}>
{fmsFields.map(({ key, label }) => (
<div key={key} style={styles.field}>
<FieldLabel>{label}</FieldLabel>
<select style={styles.input} value={String(intake.fms[key])} onChange={(e) => setFms(key, Number(e.target.value))}>
<option value="0">0</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</div>
))}
</div>
<div style={styles.checkboxGrid}>
<label style={styles.checkboxCard}>
<input type="checkbox" checked={intake.clearingTests.shoulderPainWithClearing} onChange={(e) => setClearing("shoulderPainWithClearing", e.target.checked)} />
<span>Shoulder clearing test pain</span>
</label>
<label style={styles.checkboxCard}>
<input type="checkbox" checked={intake.clearingTests.extensionPainWithClearing} onChange={(e) => setClearing("extensionPainWithClearing", e.target.checked)} />
<span>Extension clearing test pain</span>
</label>
<label style={styles.checkboxCard}>
<input type="checkbox" checked={intake.clearingTests.flexionPainWithClearing} onChange={(e) => setClearing("flexionPainWithClearing", e.target.checked)} />
<span>Flexion clearing test pain</span>
</label>
<label style={styles.checkboxCard}>
<input type="checkbox" checked={intake.clearingTests.rotaryPainWithClearing} onChange={(e) => setClearing("rotaryPainWithClearing", e.target.checked)} />
<span>Rotary clearing test pain</span>
</label>
</div>
</div>
<hr style={styles.divider} />
<div style={styles.section}>
<SectionHeader
title="Contraindications and Kinetic Chain Checkpoints"
description="Flag contraindications and note site-specific issues."
/>
<div style={styles.checkboxGrid}>
{(Object.keys(contraLabels) as ContraKey[]).map((key) => (
<label key={key} style={styles.checkboxCard}>
<input type="checkbox" checked={intake.contraindications[key]} onChange={(e) => setContra(key, e.target.checked)} />
<span>{contraLabels[key]}</span>
</label>
))}
</div>
<div style={styles.grid4}>
{(Object.keys(jointLabels) as JointKey[]).map((key) => (
<div key={key} style={styles.field}>
<FieldLabel>{jointLabels[key]}</FieldLabel>
<textarea style={styles.textarea} value={intake.kineticChain[key]} onChange={(e) => setJoint(key, e.target.value)} />
</div>
))}
</div>
</div>
<hr style={styles.divider} />
<div style={styles.infoBox}>
<div style={{ display: "flex", gap: "8px", alignItems: "center", fontWeight: 700, marginBottom: "8px" }}>
<HeartPulse size={16} />
<span>Backend packet requirements</span>
</div>
<p style={{ margin: 0, fontSize: "14px" }}>
The resistance section should now output exercise prescription in a compact table format with columns for exercise, intensity, repetitions, sets per week, tempo, and rest interval. The trainer sheet should include program emphasis such as hypertrophy, strength, or endurance. Conditioning should consider CV fitness level, resting heart rate, blood pressure, and preferred conditioning mode.
</p>
<p style={styles.smallText}>{resistanceSummary(intake.resistanceGoal)}</p>
<p style={styles.smallText}>{cardioSummary(intake.cvFitnessLevel)}</p>
</div>
<div style={styles.infoBox}>
<div style={{ fontSize: "14px", fontWeight: 700, marginBottom: "8px" }}>Resistance table preview required in PDF</div>
<table style={styles.tablePreview}>
<thead>
<tr>
<th style={styles.th}>Exercises</th>
<th style={styles.th}>Intensity</th>
<th style={styles.th}>Repetitions</th>
<th style={styles.th}>Sets/Week</th>
<th style={styles.th}>Tempo (C:I:E)</th>
<th style={styles.th}>Rest Interval Between Sets</th>
</tr>
</thead>
<tbody>
<tr>
<td style={styles.td}>Primary lift / assistance lift</td>
<td style={styles.td}>RM, body weight, kg, band, or RPE</td>
<td style={styles.td}>Goal-specific reps</td>
<td style={styles.td}>Week-by-week set structure</td>
<td style={styles.td}>1:0:1 or similar</td>
<td style={styles.td}>60-120 sec as prescribed</td>
</tr>
</tbody>
</table>
</div>
<hr style={styles.divider} />
<div style={styles.actionBox}>
<div style={styles.actionRow}>
<div>
<div style={{ fontSize: "14px", fontWeight: 700 }}>PDF Packet Output</div>
<p style={styles.smallText}>
The generated file should include a trainer reference sheet and a simplified client handout with resistance training, cardiovascular training, flexibility, and tracking fields.
</p>
<p style={styles.smallText}>
{totalBodyRuleActive
? "Frequency rule active: 3 training days or less will be programmed as total body sessions."
: "Frequency rule: split may vary because training frequency is above 3 days per week."}
</p>
</div>
<button
onClick={handleGenerate}
disabled={isSubmitting}
style={{
...styles.button,
...(isSubmitting ? styles.buttonDisabled : {}),
}}
>
<FileDown size={16} />
{isSubmitting ? "Generating PDF..." : "Generate PDF Packet"}
</button>
</div>
</div>
{status !== "idle" && (
<div
style={{
...styles.statusBox,
...(status === "success"
? {
border: "1px solid #a7f3d0",
background: "#ecfdf5",
color: "#065f46",
}
: {
border: "1px solid #fecaca",
background: "#fef2f2",
color: "#991b1b",
}),
}}
>
<div style={{ display: "flex", gap: "8px", alignItems: "flex-start" }}>
{status === "success" ? <CheckCircle2 size={16} style={{ marginTop: "2px" }} /> : <AlertCircle size={16} style={{ marginTop: "2px" }} />}
<span>{message}</span>
</div>
</div>
)}
{currentRiskMessages.length > 0 && (
<div style={styles.cautionBox}>
<div style={{ display: "flex", gap: "8px", alignItems: "center", fontWeight: 700, marginBottom: "8px" }}>
<ShieldAlert size={16} />
<span>Caution Flags</span>
</div>
<ul style={{ margin: 0, paddingLeft: "20px" }}>
{currentRiskMessages.map((item) => (
<li key={item} style={{ marginBottom: "4px" }}>{item}</li>
))}
</ul>
</div>
)}
</div>
</div>
</motion.div>
</div>
</div>
);
}