import React, { useMemo, useState } from "react"; import { motion } from "framer-motion"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Separator } from "@/components/ui/separator"; import { AlertCircle, CheckCircle2, Dumbbell, FileDown, ShieldAlert } 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 Intake = { clientName: string; trainerName: string; age: string; sex: string; weightKg: string; heightCm: string; bodyFat: string; goal: string; trainingAge: string; frequencyPerWeek: string; sessionDurationMin: string; healthHistory: string; currentMedications: string; injuryNotes: string; equipment: Record; contraindications: Record; kineticChain: Record; fms: Record; clearingTests: { shoulderPainWithClearing: boolean; extensionPainWithClearing: boolean; flexionPainWithClearing: boolean; rotaryPainWithClearing: boolean; }; }; type SubmissionPayload = { intake: Intake; rules: { training_less_than_3_days_is_total_body: boolean; include_all_7_patterns: boolean; include_primary_and_assistance_exercises: boolean; pdf_only_output: boolean; deload_every_6_to_8_weeks: boolean; progress_supported_to_integrated_then_more_closed_chain_when_tolerated: boolean; }; }; const APP_CONFIG = { appName: "Program Designer", organizationName: "The Fitclub Academy", apiBaseUrl: "/api/generate-program-pdf", pdfFilenamePrefix: "training-program", }; const equipmentLabels: Record = { gym: "Gym Machines", bands: "Resistance Bands", freeWeights: "Free Weights", bodyWeight: "Body Weight", }; const contraLabels: Record = { 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 = { footAnkle: "Foot / Ankle", knee: "Knee", hip: "Hip", lumbar: "Lumbar Spine", thoracic: "Thoracic Spine", cervical: "Cervical Spine", shoulder: "Shoulder", elbowWrist: "Elbow / Wrist", }; 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: "", goal: "General fitness and movement quality", trainingAge: "beginner", frequencyPerWeek: "3", sessionDurationMin: "60", 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, }, }; 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 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_less_than_3_days_is_total_body: true, include_all_7_patterns: true, include_primary_and_assistance_exercises: true, pdf_only_output: true, deload_every_6_to_8_weeks: true, progress_supported_to_integrated_then_more_closed_chain_when_tolerated: true, }, }; } 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 SectionHeader({ title, description }: { title: string; description: string }) { return (

{title}

{description}

); } export default function TrainingProgramDesignerEmbed() { const [intake, setIntake] = useState(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]); function setField(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(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 PDF program has been generated."); } catch (error) { setStatus("error"); setMessage(error instanceof Error ? error.message : "Unable to generate the PDF program."); } finally { setIsSubmitting(false); } } return (
{APP_CONFIG.appName} Complete the form below and generate the client program PDF.
BMI
{currentBmi ?? "—"}
FMS Total
{currentFmsTotal}
Training Split Rule
{num(intake.frequencyPerWeek, 3) < 3 ? "Total body sessions" : "Frequency-based split"}
setField("clientName", e.target.value)} />
setField("trainerName", e.target.value)} />
setField("age", e.target.value)} />
setField("sex", e.target.value)} />
setField("weightKg", e.target.value)} />
setField("heightCm", e.target.value)} />
setField("bodyFat", e.target.value)} />
setField("frequencyPerWeek", e.target.value)} />
setField("sessionDurationMin", e.target.value)} />
setField("goal", e.target.value)} />