1189 lines
57 KiB
TypeScript
1189 lines
57 KiB
TypeScript
'use client';
|
||
|
||
import React, { useState } from 'react';
|
||
import {
|
||
Dumbbell,
|
||
Flame,
|
||
Zap,
|
||
Activity,
|
||
Droplets,
|
||
Plus,
|
||
Trash2,
|
||
Scale,
|
||
Info,
|
||
CheckCircle2,
|
||
X,
|
||
Sparkles,
|
||
Calculator,
|
||
ShieldCheck,
|
||
TrendingUp,
|
||
Target,
|
||
Utensils,
|
||
Check
|
||
} from 'lucide-react';
|
||
import { WorkoutSession, DailyNutrition, MuscleRecovery, WorkoutExercise } from '@/lib/types';
|
||
import { SCIENCE_TIPS, getFormattedDate } from '@/lib/initialData';
|
||
import { playNotificationChime } from '@/lib/audio';
|
||
|
||
interface FitnessViewProps {
|
||
workouts: WorkoutSession[];
|
||
nutrition: DailyNutrition;
|
||
muscleRecovery: MuscleRecovery[];
|
||
onAddWorkout: (workout: Omit<WorkoutSession, 'id'>) => void;
|
||
onDeleteWorkout: (id: string) => void;
|
||
onUpdateNutrition: (updates: Partial<DailyNutrition>) => void;
|
||
}
|
||
|
||
export const FitnessView: React.FC<FitnessViewProps> = ({
|
||
workouts,
|
||
nutrition,
|
||
muscleRecovery,
|
||
onAddWorkout,
|
||
onDeleteWorkout,
|
||
onUpdateNutrition,
|
||
}) => {
|
||
// Modal states
|
||
const [isWorkoutModalOpen, setIsWorkoutModalOpen] = useState(false);
|
||
const [isNutritionModalOpen, setIsNutritionModalOpen] = useState(false);
|
||
|
||
// 1RM Calculator state
|
||
const [calcWeight, setCalcWeight] = useState<number>(85);
|
||
const [calcReps, setCalcReps] = useState<number>(6);
|
||
|
||
// Calculate 1RM via Epley Formula: 1RM = Weight * (1 + Reps / 30)
|
||
const estimated1RM = Math.max(1, Math.round(calcWeight * (1 + calcReps / 30)));
|
||
|
||
// Daily Macro & TDEE Calculator state
|
||
const [macroWeight, setMacroWeight] = useState<number>(nutrition.bodyweightKg || 78.5);
|
||
const [macroGoal, setMacroGoal] = useState<'cut' | 'maintain' | 'bulk'>('maintain');
|
||
const [macroActivity, setMacroActivity] = useState<'sedentary' | 'moderate' | 'intense'>('moderate');
|
||
const [isSynced, setIsSynced] = useState(false);
|
||
|
||
// Scientific Macro Calculations:
|
||
// Activity Multipliers on bodyweight: Sedentary: 30 kcal/kg, Moderate: 33 kcal/kg, Intense: 36 kcal/kg
|
||
const activityMultiplier = macroActivity === 'sedentary' ? 30 : macroActivity === 'intense' ? 36 : 33;
|
||
const maintenanceTDEE = Math.round(macroWeight * activityMultiplier);
|
||
|
||
let calcTargetCalories = maintenanceTDEE;
|
||
let calcTargetProtein = Math.round(macroWeight * 2.0); // g (optimal 2.0g/kg)
|
||
let calcTargetFats = Math.round(macroWeight * 0.9); // g
|
||
|
||
if (macroGoal === 'cut') {
|
||
calcTargetCalories = Math.round(maintenanceTDEE * 0.80); // 20% deficit
|
||
calcTargetProtein = Math.round(macroWeight * 2.3); // High protein to preserve lean body mass in a cut
|
||
calcTargetFats = Math.round(macroWeight * 0.75);
|
||
} else if (macroGoal === 'bulk') {
|
||
calcTargetCalories = Math.round(maintenanceTDEE * 1.10); // 10% clean surplus for hypertrophy
|
||
calcTargetProtein = Math.round(macroWeight * 2.0);
|
||
calcTargetFats = Math.round(macroWeight * 1.0);
|
||
}
|
||
|
||
// Carbs = Remaining calories / 4 kcal per gram (primary glycogen driver)
|
||
const caloriesFromProteinAndFat = (calcTargetProtein * 4) + (calcTargetFats * 9);
|
||
const calcTargetCarbs = Math.max(40, Math.round((calcTargetCalories - caloriesFromProteinAndFat) / 4));
|
||
|
||
const handleApplyMacroTargets = () => {
|
||
onUpdateNutrition({
|
||
calorieTarget: calcTargetCalories,
|
||
proteinTarget: calcTargetProtein,
|
||
carbsTarget: calcTargetCarbs,
|
||
fatsTarget: calcTargetFats,
|
||
bodyweightKg: macroWeight,
|
||
});
|
||
setIsSynced(true);
|
||
playNotificationChime('complete');
|
||
setTimeout(() => setIsSynced(false), 2500);
|
||
};
|
||
|
||
// Log Workout Form State
|
||
const [workoutName, setWorkoutName] = useState('Push Hypertrophy');
|
||
const [workoutDate, setWorkoutDate] = useState(getFormattedDate(0));
|
||
const [workoutMuscles, setWorkoutMuscles] = useState('Chest, Shoulders, Triceps');
|
||
const [workoutNotes, setWorkoutNotes] = useState('');
|
||
const [exerciseList, setExerciseList] = useState<{
|
||
name: string;
|
||
muscleGroup: string;
|
||
weight: number;
|
||
reps: number;
|
||
rpe: number;
|
||
setsCount: number;
|
||
}[]>([
|
||
{ name: 'Incline Dumbbell Press', muscleGroup: 'Chest', weight: 32, reps: 8, rpe: 8.5, setsCount: 3 },
|
||
]);
|
||
|
||
const addExerciseRow = () => {
|
||
setExerciseList([
|
||
...exerciseList,
|
||
{ name: '', muscleGroup: 'Chest', weight: 60, reps: 8, rpe: 8, setsCount: 3 },
|
||
]);
|
||
};
|
||
|
||
const removeExerciseRow = (index: number) => {
|
||
setExerciseList(exerciseList.filter((_, i) => i !== index));
|
||
};
|
||
|
||
const handleCreateWorkout = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!workoutName.trim() || exerciseList.length === 0) return;
|
||
|
||
let totalVolume = 0;
|
||
const constructedExercises: WorkoutExercise[] = exerciseList.map((ex) => {
|
||
const sets = [];
|
||
for (let s = 1; s <= ex.setsCount; s++) {
|
||
sets.push({
|
||
setNumber: s,
|
||
weight: ex.weight,
|
||
reps: ex.reps,
|
||
rpe: ex.rpe,
|
||
});
|
||
totalVolume += ex.weight * ex.reps;
|
||
}
|
||
return {
|
||
name: ex.name || 'Exercise',
|
||
muscleGroup: ex.muscleGroup,
|
||
sets,
|
||
};
|
||
});
|
||
|
||
onAddWorkout({
|
||
name: workoutName.trim(),
|
||
date: workoutDate,
|
||
muscleGroups: workoutMuscles.split(',').map((m) => m.trim()),
|
||
volumeLoad: totalVolume,
|
||
exercises: constructedExercises,
|
||
notes: workoutNotes.trim() || undefined,
|
||
});
|
||
|
||
setIsWorkoutModalOpen(false);
|
||
playNotificationChime('streak');
|
||
};
|
||
|
||
// Water Increment / Decrement
|
||
const handleWaterChange = (delta: number) => {
|
||
const updated = Math.max(0, parseFloat((nutrition.waterLiters + delta).toFixed(2)));
|
||
onUpdateNutrition({ waterLiters: updated });
|
||
playNotificationChime('click');
|
||
};
|
||
|
||
// Creatine Toggle
|
||
const handleCreatineToggle = () => {
|
||
onUpdateNutrition({ creatineTaken: !nutrition.creatineTaken });
|
||
playNotificationChime(nutrition.creatineTaken ? 'click' : 'complete');
|
||
};
|
||
|
||
// Nutrition Form State
|
||
const [editCalories, setEditCalories] = useState(nutrition.calories);
|
||
const [editProtein, setEditProtein] = useState(nutrition.protein);
|
||
const [editCarbs, setEditCarbs] = useState(nutrition.carbs);
|
||
const [editFats, setEditFats] = useState(nutrition.fats);
|
||
const [editWeight, setEditWeight] = useState(nutrition.bodyweightKg);
|
||
|
||
const handleSaveNutrition = (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
onUpdateNutrition({
|
||
calories: Number(editCalories),
|
||
protein: Number(editProtein),
|
||
carbs: Number(editCarbs),
|
||
fats: Number(editFats),
|
||
bodyweightKg: Number(editWeight),
|
||
});
|
||
setIsNutritionModalOpen(false);
|
||
playNotificationChime('click');
|
||
};
|
||
|
||
const proteinRatio = (nutrition.protein / nutrition.bodyweightKg).toFixed(2);
|
||
const isOptimalProtein = parseFloat(proteinRatio) >= 1.6;
|
||
|
||
return (
|
||
<div className="space-y-8 pb-12">
|
||
{/* Header Banner */}
|
||
<div className="relative overflow-hidden rounded-3xl bg-gradient-to-r from-[#0d1f18] via-[#0d1627] to-[#121c33] border border-lime-500/30 p-6 md:p-8 shadow-2xl">
|
||
<div className="absolute -right-12 -top-12 w-64 h-64 bg-lime-500/10 rounded-full blur-3xl pointer-events-none" />
|
||
<div className="absolute -left-12 -bottom-12 w-64 h-64 bg-cyan-500/10 rounded-full blur-3xl pointer-events-none" />
|
||
|
||
<div className="relative z-10 flex flex-col md:flex-row md:items-center justify-between gap-6">
|
||
<div className="space-y-2">
|
||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-lime-500/10 border border-lime-500/30 text-lime-400 text-xs font-semibold tracking-wide">
|
||
<Zap className="w-3.5 h-3.5" />
|
||
<span>Evidence-Based Hypertrophy & Metabolism Lab</span>
|
||
</div>
|
||
<h2 className="text-2xl md:text-3xl font-extrabold text-white tracking-tight">
|
||
Science of <span className="bg-clip-text text-transparent bg-gradient-to-r from-lime-400 to-emerald-400">Progressive Overload</span>
|
||
</h2>
|
||
<p className="text-sm text-slate-400 max-w-2xl">
|
||
Tension over guesswork. Track volume load (kg × reps), monitor your Muscle Protein Synthesis (MPS) recovery windows, and hit precise macronutrient thresholds for optimal adaptation.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex flex-wrap gap-3">
|
||
<button
|
||
onClick={() => setIsWorkoutModalOpen(true)}
|
||
className="flex items-center gap-2 px-5 py-3 rounded-2xl bg-gradient-to-r from-lime-500 to-emerald-500 text-slate-950 font-bold text-sm shadow-lg shadow-lime-500/20 hover:scale-105 active:scale-95 transition-all cursor-pointer"
|
||
>
|
||
<Dumbbell className="w-4 h-4" />
|
||
<span>Log Scientific Workout</span>
|
||
</button>
|
||
<button
|
||
onClick={() => setIsNutritionModalOpen(true)}
|
||
className="flex items-center gap-2 px-5 py-3 rounded-2xl bg-slate-900 border border-slate-800 hover:bg-slate-800 text-slate-200 font-semibold text-sm transition-all hover:scale-105 active:scale-95 cursor-pointer"
|
||
>
|
||
<Scale className="w-4 h-4 text-lime-400" />
|
||
<span>Adjust Macros</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* SECTION 1: MACRONUTRIENT & METABOLIC FUEL STATION */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<Flame className="w-5 h-5 text-lime-400" />
|
||
<h3 className="text-lg font-bold text-slate-100">Daily Macronutrient Fuel Station</h3>
|
||
</div>
|
||
<span className="text-xs text-slate-400">Bodyweight: <strong className="text-white">{nutrition.bodyweightKg} kg</strong></span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||
{/* Calories Card */}
|
||
<div className="glass-card p-5 border-l-4 border-l-lime-500">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Total Energy (Calories)</span>
|
||
<span className="text-xs font-bold text-lime-400">
|
||
{Math.round((nutrition.calories / nutrition.calorieTarget) * 100)}%
|
||
</span>
|
||
</div>
|
||
<div className="mt-3 flex items-baseline gap-2">
|
||
<span className="text-3xl font-extrabold text-white">{nutrition.calories}</span>
|
||
<span className="text-xs text-slate-400">/ {nutrition.calorieTarget} kcal</span>
|
||
</div>
|
||
<div className="w-full h-2 bg-slate-900 rounded-full mt-3 overflow-hidden border border-slate-800">
|
||
<div
|
||
className="h-full bg-lime-400 rounded-full"
|
||
style={{ width: `${Math.min(100, (nutrition.calories / nutrition.calorieTarget) * 100)}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Protein Card (Scientific g/kg metric) */}
|
||
<div className="glass-card p-5 border-l-4 border-l-cyan-500">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Protein (MPS)</span>
|
||
<span className={`text-[10px] font-bold px-2 py-0.5 rounded-full border ${
|
||
isOptimalProtein ? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30' : 'bg-amber-500/10 text-amber-400 border-amber-500/30'
|
||
}`}>
|
||
{proteinRatio} g/kg
|
||
</span>
|
||
</div>
|
||
<div className="mt-3 flex items-baseline gap-2">
|
||
<span className="text-3xl font-extrabold text-white">{nutrition.protein}g</span>
|
||
<span className="text-xs text-slate-400">/ {nutrition.proteinTarget}g target</span>
|
||
</div>
|
||
<p className="text-[11px] text-slate-400 mt-2 flex items-center gap-1">
|
||
<ShieldCheck className="w-3.5 h-3.5 text-cyan-400" />
|
||
<span>Optimal range: 1.6 - 2.2 g/kg</span>
|
||
</p>
|
||
</div>
|
||
|
||
{/* Carbohydrates & Fats */}
|
||
<div className="glass-card p-5 border-l-4 border-l-amber-500">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Glycogen & Hormones</span>
|
||
<span className="text-xs text-slate-400">Carbs & Fats</span>
|
||
</div>
|
||
<div className="mt-3 flex items-center justify-between">
|
||
<div>
|
||
<span className="text-xl font-bold text-amber-400">{nutrition.carbs}g</span>
|
||
<p className="text-[10px] text-slate-400">Carbs (Glycogen)</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<span className="text-xl font-bold text-orange-400">{nutrition.fats}g</span>
|
||
<p className="text-[10px] text-slate-400">Fats (Lipids)</p>
|
||
</div>
|
||
</div>
|
||
<div className="w-full h-2 bg-slate-900 rounded-full mt-3 overflow-hidden border border-slate-800 flex">
|
||
<div
|
||
className="bg-amber-400 h-full"
|
||
style={{ width: `${Math.min(65, (nutrition.carbs / nutrition.carbsTarget) * 65)}%` }}
|
||
/>
|
||
<div
|
||
className="bg-orange-400 h-full ml-1"
|
||
style={{ width: `${Math.min(35, (nutrition.fats / nutrition.fatsTarget) * 35)}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Hydration & Creatine Station */}
|
||
<div className="glass-card p-5 border-l-4 border-l-blue-500 flex flex-col justify-between">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Hydration & Ergogenics</span>
|
||
<Droplets className="w-4 h-4 text-blue-400" />
|
||
</div>
|
||
|
||
<div className="my-2 flex items-center justify-between">
|
||
<div>
|
||
<span className="text-2xl font-extrabold text-white">{nutrition.waterLiters}L</span>
|
||
<p className="text-[10px] text-slate-400">Water Intake</p>
|
||
</div>
|
||
<div className="flex items-center gap-1">
|
||
<button
|
||
onClick={() => handleWaterChange(-0.25)}
|
||
className="w-7 h-7 rounded-lg bg-slate-900 border border-slate-800 text-xs font-bold text-slate-300 hover:text-white"
|
||
title="Remove 250ml"
|
||
>
|
||
-
|
||
</button>
|
||
<button
|
||
onClick={() => handleWaterChange(0.25)}
|
||
className="w-7 h-7 rounded-lg bg-blue-500/20 border border-blue-500/40 text-xs font-bold text-blue-300 hover:bg-blue-500/30"
|
||
title="Add 250ml"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Creatine Checkbox */}
|
||
<button
|
||
onClick={handleCreatineToggle}
|
||
className={`w-full py-1.5 px-3 rounded-xl border flex items-center justify-between text-xs font-semibold transition-all cursor-pointer ${
|
||
nutrition.creatineTaken
|
||
? 'bg-lime-500/10 border-lime-500/40 text-lime-400'
|
||
: 'bg-slate-900 border-slate-800 text-slate-400 hover:border-slate-700'
|
||
}`}
|
||
>
|
||
<span>Creatine Monohydrate (5g)</span>
|
||
<CheckCircle2 className={`w-4 h-4 ${nutrition.creatineTaken ? 'fill-lime-400/20 stroke-lime-400' : 'opacity-40'}`} />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* SECTION 2: MUSCLE RECOVERY & PROTEIN SYNTHESIS MATRIX */}
|
||
<div className="glass-card p-6 space-y-4">
|
||
<div className="flex items-center justify-between border-b border-slate-800 pb-3">
|
||
<div>
|
||
<h3 className="text-base font-bold text-slate-100 flex items-center gap-2">
|
||
<Activity className="w-5 h-5 text-lime-400" />
|
||
<span>Muscle Protein Synthesis (MPS) & Recovery Matrix</span>
|
||
</h3>
|
||
<p className="text-xs text-slate-400 mt-0.5">
|
||
Based on the 48–72h physiological recovery window required for tissue remodeling.
|
||
</p>
|
||
</div>
|
||
<span className="text-xs px-2.5 py-1 rounded-full bg-slate-900 border border-slate-800 text-slate-400 font-semibold hidden sm:inline">
|
||
Hypertrophy Window
|
||
</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{muscleRecovery.map((rec) => {
|
||
const isReady = rec.status === 'ready';
|
||
const isRecovering = rec.status === 'recovering';
|
||
|
||
return (
|
||
<div
|
||
key={rec.muscle}
|
||
className="p-4 rounded-xl bg-slate-900/80 border border-slate-800 flex flex-col justify-between space-y-3"
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-sm font-bold text-slate-200">{rec.muscle}</span>
|
||
<span className={`text-[10px] font-extrabold uppercase px-2 py-0.5 rounded-full border ${
|
||
isReady
|
||
? 'bg-emerald-500/10 text-emerald-400 border-emerald-500/30'
|
||
: isRecovering
|
||
? 'bg-amber-500/10 text-amber-400 border-amber-500/30'
|
||
: 'bg-rose-500/10 text-rose-400 border-rose-500/30'
|
||
}`}>
|
||
{rec.status}
|
||
</span>
|
||
</div>
|
||
|
||
<div>
|
||
<div className="flex items-center justify-between text-xs text-slate-400 mb-1">
|
||
<span>Readiness: {rec.percentRecovered}%</span>
|
||
<span>Trained {rec.lastTrainedDaysAgo === 0 ? 'Today' : `${rec.lastTrainedDaysAgo}d ago`}</span>
|
||
</div>
|
||
<div className="w-full h-2 bg-slate-950 rounded-full overflow-hidden border border-slate-800">
|
||
<div
|
||
className={`h-full rounded-full transition-all ${
|
||
isReady ? 'bg-emerald-400' : isRecovering ? 'bg-amber-400' : 'bg-rose-400'
|
||
}`}
|
||
style={{ width: `${rec.percentRecovered}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* SECTION 3: INTERACTIVE SCIENTIFIC FITNESS TOOLS & CALCULATORS */}
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-2">
|
||
<Calculator className="w-5 h-5 text-cyan-400" />
|
||
<h3 className="text-lg font-bold text-slate-100">Interactive Scientific Fitness Tools</h3>
|
||
</div>
|
||
<span className="text-xs text-slate-400">Precision Biometrics & Training Zones</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
{/* TOOL 1: 1RM & Percentage Training Zones Calculator */}
|
||
<div className="glass-card p-6 flex flex-col justify-between space-y-5 border-t-4 border-t-cyan-500">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-1">
|
||
<div className="flex items-center gap-2">
|
||
<Dumbbell className="w-5 h-5 text-cyan-400" />
|
||
<h4 className="font-bold text-slate-100 text-base">One-Rep Max (1RM) Calculator</h4>
|
||
</div>
|
||
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-cyan-500/10 text-cyan-300 border border-cyan-500/30">
|
||
Epley Formula
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-slate-400 leading-relaxed mb-4">
|
||
Calculate estimated max strength without injury risk and identify your exact percentage training zones.
|
||
</p>
|
||
|
||
{/* Weight Input with Steppers & Quick Chips */}
|
||
<div className="space-y-3 p-4 rounded-xl bg-slate-900/80 border border-slate-800">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-xs font-semibold text-slate-300">Weight Lifted</label>
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setCalcWeight(Math.max(5, calcWeight - 2.5))}
|
||
className="w-6 h-6 rounded bg-slate-800 text-slate-300 hover:text-white text-xs font-bold cursor-pointer"
|
||
>
|
||
-
|
||
</button>
|
||
<span className="text-sm font-bold text-cyan-400 font-mono w-16 text-center">{calcWeight} kg</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => setCalcWeight(calcWeight + 2.5)}
|
||
className="w-6 h-6 rounded bg-slate-800 text-slate-300 hover:text-white text-xs font-bold cursor-pointer"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<input
|
||
type="range"
|
||
min="20"
|
||
max="250"
|
||
step="2.5"
|
||
value={calcWeight}
|
||
onChange={(e) => setCalcWeight(Number(e.target.value))}
|
||
className="w-full accent-cyan-400 cursor-pointer"
|
||
/>
|
||
|
||
{/* Quick Presets */}
|
||
<div className="flex items-center gap-1.5 pt-1">
|
||
<span className="text-[10px] text-slate-500 mr-1">Presets:</span>
|
||
{[60, 80, 100, 120, 140].map((w) => (
|
||
<button
|
||
key={w}
|
||
type="button"
|
||
onClick={() => setCalcWeight(w)}
|
||
className={`text-[10px] px-2 py-0.5 rounded-md border font-semibold transition-colors cursor-pointer ${
|
||
calcWeight === w
|
||
? 'bg-cyan-500/20 text-cyan-300 border-cyan-500/50'
|
||
: 'bg-slate-950 text-slate-400 border-slate-800 hover:text-slate-200'
|
||
}`}
|
||
>
|
||
{w}kg
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Reps Input with Segmented Pills */}
|
||
<div className="space-y-2 mt-4 p-4 rounded-xl bg-slate-900/80 border border-slate-800">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-xs font-semibold text-slate-300">Reps Completed</label>
|
||
<span className="text-sm font-bold text-cyan-400 font-mono">{calcReps} reps</span>
|
||
</div>
|
||
<div className="grid grid-cols-5 sm:grid-cols-9 gap-1.5 pt-1">
|
||
{[1, 2, 3, 4, 5, 6, 8, 10, 12].map((r) => (
|
||
<button
|
||
key={r}
|
||
type="button"
|
||
onClick={() => setCalcReps(r)}
|
||
className={`py-1.5 rounded-lg text-xs font-bold transition-all cursor-pointer ${
|
||
calcReps === r
|
||
? 'bg-cyan-500 text-slate-950 shadow-md shadow-cyan-500/30'
|
||
: 'bg-slate-950 text-slate-400 border border-slate-800 hover:text-slate-200'
|
||
}`}
|
||
>
|
||
{r}
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Estimated 1RM Readout */}
|
||
<div className="mt-4 p-4 rounded-2xl bg-cyan-950/20 border border-cyan-500/40 text-center relative overflow-hidden">
|
||
<div className="absolute top-0 right-0 p-2 text-cyan-400 opacity-20 pointer-events-none">
|
||
<Sparkles className="w-12 h-12" />
|
||
</div>
|
||
<span className="text-xs font-bold text-cyan-300 uppercase tracking-wider">Estimated 1-Rep Max</span>
|
||
<div className="text-4xl font-black text-white mt-1 tracking-tight">
|
||
{estimated1RM} <span className="text-lg font-bold text-cyan-400">kg</span>
|
||
</div>
|
||
<p className="text-[11px] text-slate-400 mt-1 font-mono">
|
||
1RM = {calcWeight}kg × (1 + {calcReps}/30)
|
||
</p>
|
||
</div>
|
||
|
||
{/* Explicit Percentage Training Zones: 90%, 80%, 70% */}
|
||
<div className="mt-4 space-y-2">
|
||
<div className="text-xs font-bold text-slate-400 uppercase tracking-wider">Percentage Training Zones</div>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
{/* 90% Zone */}
|
||
<div className="p-3 rounded-xl bg-slate-900 border border-rose-500/30 text-center space-y-1">
|
||
<span className="text-[11px] font-extrabold text-rose-400 uppercase tracking-wider block">90% 1RM</span>
|
||
<div className="text-xl font-extrabold text-white">{Math.round(estimated1RM * 0.90)}kg</div>
|
||
<span className="text-[10px] text-slate-300 font-semibold block">3 - 4 Reps</span>
|
||
<span className="text-[9px] text-slate-500 block">Max Strength & Power</span>
|
||
</div>
|
||
|
||
{/* 80% Zone */}
|
||
<div className="p-3 rounded-xl bg-slate-900 border border-amber-500/30 text-center space-y-1">
|
||
<span className="text-[11px] font-extrabold text-amber-400 uppercase tracking-wider block">80% 1RM</span>
|
||
<div className="text-xl font-extrabold text-white">{Math.round(estimated1RM * 0.80)}kg</div>
|
||
<span className="text-[10px] text-slate-300 font-semibold block">6 - 8 Reps</span>
|
||
<span className="text-[9px] text-slate-500 block">Heavy Hypertrophy</span>
|
||
</div>
|
||
|
||
{/* 70% Zone */}
|
||
<div className="p-3 rounded-xl bg-slate-900 border border-cyan-500/30 text-center space-y-1">
|
||
<span className="text-[11px] font-extrabold text-cyan-400 uppercase tracking-wider block">70% 1RM</span>
|
||
<div className="text-xl font-extrabold text-white">{Math.round(estimated1RM * 0.70)}kg</div>
|
||
<span className="text-[10px] text-slate-300 font-semibold block">10 - 12 Reps</span>
|
||
<span className="text-[9px] text-slate-500 block">Volume & Endurance</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* TOOL 2: Daily Macro Calculator */}
|
||
<div className="glass-card p-6 flex flex-col justify-between space-y-5 border-t-4 border-t-lime-500">
|
||
<div>
|
||
<div className="flex items-center justify-between mb-1">
|
||
<div className="flex items-center gap-2">
|
||
<Utensils className="w-5 h-5 text-lime-400" />
|
||
<h4 className="font-bold text-slate-100 text-base">Scientific Daily Macro Calculator</h4>
|
||
</div>
|
||
<span className="text-[10px] font-bold px-2 py-0.5 rounded-full bg-lime-500/10 text-lime-300 border border-lime-500/30">
|
||
TDEE + MPS
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-slate-400 leading-relaxed mb-4">
|
||
Calculate custom macronutrient requirements based on your current bodyweight and physique goal.
|
||
</p>
|
||
|
||
{/* Bodyweight Input */}
|
||
<div className="space-y-3 p-4 rounded-xl bg-slate-900/80 border border-slate-800">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-xs font-semibold text-slate-300">Current Bodyweight</label>
|
||
<div className="flex items-center gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroWeight(Math.max(40, parseFloat((macroWeight - 0.5).toFixed(1))))}
|
||
className="w-6 h-6 rounded bg-slate-800 text-slate-300 hover:text-white text-xs font-bold cursor-pointer"
|
||
>
|
||
-
|
||
</button>
|
||
<span className="text-sm font-bold text-lime-400 font-mono w-20 text-center">{macroWeight} kg</span>
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroWeight(parseFloat((macroWeight + 0.5).toFixed(1)))}
|
||
className="w-6 h-6 rounded bg-slate-800 text-slate-300 hover:text-white text-xs font-bold cursor-pointer"
|
||
>
|
||
+
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<input
|
||
type="range"
|
||
min="50"
|
||
max="130"
|
||
step="0.5"
|
||
value={macroWeight}
|
||
onChange={(e) => setMacroWeight(Number(e.target.value))}
|
||
className="w-full accent-lime-400 cursor-pointer"
|
||
/>
|
||
</div>
|
||
|
||
{/* Goal Selector: Cut, Maintain, Lean Bulk */}
|
||
<div className="space-y-2 mt-4">
|
||
<label className="text-xs font-semibold text-slate-300">Physique Goal</label>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroGoal('cut')}
|
||
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
|
||
macroGoal === 'cut'
|
||
? 'bg-rose-500/10 border-rose-500 text-rose-300 shadow-md shadow-rose-500/20'
|
||
: 'bg-slate-900 border-slate-800 text-slate-400 hover:text-slate-200'
|
||
}`}
|
||
>
|
||
<span className="text-xs font-bold block">🔥 Cut</span>
|
||
<span className="text-[10px] text-slate-400 block mt-0.5">-20% Deficit</span>
|
||
<span className="text-[9px] text-rose-400 block font-semibold">2.3g/kg Protein</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroGoal('maintain')}
|
||
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
|
||
macroGoal === 'maintain'
|
||
? 'bg-cyan-500/10 border-cyan-500 text-cyan-300 shadow-md shadow-cyan-500/20'
|
||
: 'bg-slate-900 border-slate-800 text-slate-400 hover:text-slate-200'
|
||
}`}
|
||
>
|
||
<span className="text-xs font-bold block">⚖️ Maintain</span>
|
||
<span className="text-[10px] text-slate-400 block mt-0.5">TDEE Balance</span>
|
||
<span className="text-[9px] text-cyan-400 block font-semibold">2.0g/kg Protein</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroGoal('bulk')}
|
||
className={`p-3 rounded-xl border text-left transition-all cursor-pointer ${
|
||
macroGoal === 'bulk'
|
||
? 'bg-lime-500/10 border-lime-500 text-lime-300 shadow-md shadow-lime-500/20'
|
||
: 'bg-slate-900 border-slate-800 text-slate-400 hover:text-slate-200'
|
||
}`}
|
||
>
|
||
<span className="text-xs font-bold block">🚀 Lean Bulk</span>
|
||
<span className="text-[10px] text-slate-400 block mt-0.5">+10% Surplus</span>
|
||
<span className="text-[9px] text-lime-400 block font-semibold">High Glycogen</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Activity Level Selector */}
|
||
<div className="space-y-1.5 mt-4">
|
||
<label className="text-xs font-semibold text-slate-300">Weekly Training Frequency</label>
|
||
<div className="grid grid-cols-3 gap-2 text-center text-xs">
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroActivity('sedentary')}
|
||
className={`py-1.5 px-2 rounded-lg border text-xs font-medium transition-colors cursor-pointer ${
|
||
macroActivity === 'sedentary'
|
||
? 'bg-slate-800 text-white border-slate-600'
|
||
: 'bg-slate-950 text-slate-400 border-slate-800'
|
||
}`}
|
||
>
|
||
1-2 Days/Wk
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroActivity('moderate')}
|
||
className={`py-1.5 px-2 rounded-lg border text-xs font-medium transition-colors cursor-pointer ${
|
||
macroActivity === 'moderate'
|
||
? 'bg-slate-800 text-lime-300 border-lime-500/50'
|
||
: 'bg-slate-950 text-slate-400 border-slate-800'
|
||
}`}
|
||
>
|
||
3-4 Days/Wk
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={() => setMacroActivity('intense')}
|
||
className={`py-1.5 px-2 rounded-lg border text-xs font-medium transition-colors cursor-pointer ${
|
||
macroActivity === 'intense'
|
||
? 'bg-slate-800 text-lime-300 border-lime-500/50'
|
||
: 'bg-slate-950 text-slate-400 border-slate-800'
|
||
}`}
|
||
>
|
||
5-6 Days/Wk
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Calculated Targets Output */}
|
||
<div className="mt-4 p-4 rounded-2xl bg-slate-900 border border-slate-800 space-y-3">
|
||
<div className="flex items-baseline justify-between border-b border-slate-800/80 pb-2">
|
||
<span className="text-xs font-semibold text-slate-400 uppercase tracking-wider">Target Daily Calories</span>
|
||
<span className="text-2xl font-black text-white font-mono">{calcTargetCalories} <span className="text-sm font-bold text-lime-400">kcal</span></span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 gap-2 text-center pt-1">
|
||
<div className="p-2 rounded-lg bg-slate-950 border border-cyan-500/30">
|
||
<span className="text-[10px] text-cyan-400 font-bold uppercase block">Protein</span>
|
||
<span className="text-base font-extrabold text-white">{calcTargetProtein}g</span>
|
||
<span className="text-[9px] text-slate-500 block">{(calcTargetProtein / macroWeight).toFixed(1)} g/kg</span>
|
||
</div>
|
||
<div className="p-2 rounded-lg bg-slate-950 border border-amber-500/30">
|
||
<span className="text-[10px] text-amber-400 font-bold uppercase block">Carbs</span>
|
||
<span className="text-base font-extrabold text-white">{calcTargetCarbs}g</span>
|
||
<span className="text-[9px] text-slate-500 block">Glycogen</span>
|
||
</div>
|
||
<div className="p-2 rounded-lg bg-slate-950 border border-orange-500/30">
|
||
<span className="text-[10px] text-orange-400 font-bold uppercase block">Fats</span>
|
||
<span className="text-base font-extrabold text-white">{calcTargetFats}g</span>
|
||
<span className="text-[9px] text-slate-500 block">Hormones</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Macro Distribution Bar */}
|
||
<div className="w-full h-2 rounded-full overflow-hidden flex bg-slate-950">
|
||
<div style={{ width: `${Math.round(((calcTargetProtein * 4) / calcTargetCalories) * 100)}%` }} className="bg-cyan-400 h-full" title="Protein" />
|
||
<div style={{ width: `${Math.round(((calcTargetCarbs * 4) / calcTargetCalories) * 100)}%` }} className="bg-amber-400 h-full" title="Carbs" />
|
||
<div style={{ width: `${Math.round(((calcTargetFats * 9) / calcTargetCalories) * 100)}%` }} className="bg-orange-400 h-full" title="Fats" />
|
||
</div>
|
||
</div>
|
||
|
||
{/* Sync Button */}
|
||
<button
|
||
type="button"
|
||
onClick={handleApplyMacroTargets}
|
||
className={`mt-4 w-full py-2.5 rounded-xl font-bold text-xs transition-all flex items-center justify-center gap-2 cursor-pointer shadow-lg ${
|
||
isSynced
|
||
? 'bg-emerald-500 text-slate-950 shadow-emerald-500/30'
|
||
: 'bg-gradient-to-r from-lime-500 to-emerald-500 text-slate-950 hover:scale-102 active:scale-98 shadow-lime-500/20'
|
||
}`}
|
||
>
|
||
{isSynced ? (
|
||
<>
|
||
<Check className="w-4 h-4 stroke-[3]" />
|
||
<span>Applied to Daily Fuel Station!</span>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Target className="w-4 h-4" />
|
||
<span>Apply to Dashboard Fuel Station</span>
|
||
</>
|
||
)}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Evidence-Based Science Principles */}
|
||
<div className="glass-card p-6 space-y-4">
|
||
<div className="flex items-center gap-2 border-b border-slate-800 pb-3">
|
||
<Info className="w-5 h-5 text-lime-400" />
|
||
<h3 className="font-bold text-slate-100 text-base">Evidence-Based Hypertrophy Principles</h3>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-3">
|
||
{SCIENCE_TIPS.map((tip) => (
|
||
<div key={tip.title} className="p-4 rounded-xl bg-slate-900/60 border border-slate-800/80 space-y-1">
|
||
<div className="flex items-center justify-between">
|
||
<h4 className="text-xs font-bold text-lime-300">{tip.title}</h4>
|
||
<span className="text-[10px] px-2 py-0.2 rounded-full bg-slate-800 text-slate-400 font-semibold border border-slate-700">
|
||
{tip.concept}
|
||
</span>
|
||
</div>
|
||
<p className="text-xs text-slate-300 leading-relaxed pt-1">{tip.description}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* SECTION 4: PROGRESSIVE OVERLOAD WORKOUT LOG */}
|
||
<div className="glass-card overflow-hidden">
|
||
<div className="p-5 border-b border-slate-800 flex items-center justify-between">
|
||
<div>
|
||
<h3 className="text-base font-bold text-slate-100 flex items-center gap-2">
|
||
<TrendingUp className="w-5 h-5 text-lime-400" />
|
||
<span>Scientific Training Log & Volume Load</span>
|
||
</h3>
|
||
<p className="text-xs text-slate-400 mt-0.5">
|
||
Track Volume Load (Sets × Reps × Weight in kg) to ensure progressive overload across weeks.
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => setIsWorkoutModalOpen(true)}
|
||
className="flex items-center gap-2 px-4 py-2 rounded-xl bg-gradient-to-r from-lime-500 to-emerald-500 text-slate-950 text-xs font-bold shadow-md shadow-lime-500/20 hover:scale-105 active:scale-95 transition-all cursor-pointer"
|
||
>
|
||
<Plus className="w-3.5 h-3.5" />
|
||
<span>New Session</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="divide-y divide-slate-800/70">
|
||
{workouts.map((session) => (
|
||
<div key={session.id} className="p-6 space-y-4 hover:bg-slate-900/30 transition-colors group">
|
||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||
<div>
|
||
<div className="flex items-center gap-3">
|
||
<h4 className="text-base font-bold text-white">{session.name}</h4>
|
||
<span className="text-xs text-slate-400 font-mono">[{session.date}]</span>
|
||
</div>
|
||
<div className="flex flex-wrap items-center gap-1.5 mt-1.5">
|
||
{session.muscleGroups.map((mg) => (
|
||
<span key={mg} className="text-[10px] px-2 py-0.5 rounded-full bg-slate-900 border border-slate-800 text-lime-400 font-semibold">
|
||
{mg}
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-4">
|
||
<div className="text-right">
|
||
<span className="text-xs text-slate-400 block uppercase font-bold tracking-wider">Total Volume Load</span>
|
||
<span className="text-base font-extrabold text-lime-400 font-mono">
|
||
{session.volumeLoad.toLocaleString()} kg
|
||
</span>
|
||
</div>
|
||
|
||
<button
|
||
onClick={() => onDeleteWorkout(session.id)}
|
||
className="p-1.5 text-slate-500 hover:text-red-400 rounded transition-colors"
|
||
title="Delete workout log"
|
||
>
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Exercise breakdown grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
|
||
{session.exercises.map((ex, idx) => (
|
||
<div key={idx} className="p-3.5 rounded-xl bg-slate-900/80 border border-slate-800 space-y-2">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-bold text-slate-200 truncate">{ex.name}</span>
|
||
<span className="text-[10px] text-slate-400 uppercase font-semibold">{ex.muscleGroup}</span>
|
||
</div>
|
||
|
||
<div className="space-y-1">
|
||
{ex.sets.map((s) => (
|
||
<div key={s.setNumber} className="flex items-center justify-between text-xs text-slate-400 bg-slate-950/60 px-2 py-1 rounded">
|
||
<span className="font-semibold text-slate-300">Set {s.setNumber}</span>
|
||
<span>{s.weight} kg × {s.reps} reps</span>
|
||
<span className={`text-[10px] font-bold px-1.5 py-0.2 rounded border ${
|
||
s.rpe >= 9 ? 'bg-red-500/10 text-red-400 border-red-500/30' : 'bg-lime-500/10 text-lime-400 border-lime-500/30'
|
||
}`}>
|
||
RPE {s.rpe}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{session.notes && (
|
||
<p className="text-xs text-slate-400 italic bg-slate-950/40 p-2.5 rounded-lg border border-slate-900">
|
||
💡 {session.notes}
|
||
</p>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
{workouts.length === 0 && (
|
||
<div className="py-12 text-center text-slate-500 text-xs">
|
||
No workouts logged yet. Start recording your sessions to build a progressive overload history!
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* MODAL: LOG SCIENTIFIC WORKOUT */}
|
||
{isWorkoutModalOpen && (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm animate-in fade-in duration-200">
|
||
<div className="relative w-full max-w-2xl rounded-2xl bg-[#0e1424] border border-lime-500/30 p-6 shadow-2xl space-y-4 max-h-[90vh] overflow-y-auto">
|
||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||
<h3 className="text-base font-bold text-white flex items-center gap-2">
|
||
<Dumbbell className="w-4 h-4 text-lime-400" />
|
||
Log Scientific Workout Session
|
||
</h3>
|
||
<button
|
||
onClick={() => setIsWorkoutModalOpen(false)}
|
||
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-slate-800"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<form onSubmit={handleCreateWorkout} className="space-y-4">
|
||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||
<div className="sm:col-span-2">
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Session Name *</label>
|
||
<input
|
||
type="text"
|
||
required
|
||
placeholder="e.g. Push Hypertrophy, Legs & Core"
|
||
value={workoutName}
|
||
onChange={(e) => setWorkoutName(e.target.value)}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Date</label>
|
||
<input
|
||
type="date"
|
||
value={workoutDate}
|
||
onChange={(e) => setWorkoutDate(e.target.value)}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Target Muscle Groups (comma separated)</label>
|
||
<input
|
||
type="text"
|
||
placeholder="Chest, Shoulders, Triceps"
|
||
value={workoutMuscles}
|
||
onChange={(e) => setWorkoutMuscles(e.target.value)}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
|
||
{/* Dynamic Exercise Rows */}
|
||
<div className="space-y-3 pt-2">
|
||
<div className="flex items-center justify-between">
|
||
<label className="text-xs font-bold text-lime-400 uppercase tracking-wider">Exercise List</label>
|
||
<button
|
||
type="button"
|
||
onClick={addExerciseRow}
|
||
className="text-xs text-lime-400 hover:text-lime-300 font-semibold flex items-center gap-1"
|
||
>
|
||
<Plus className="w-3.5 h-3.5" /> Add Exercise
|
||
</button>
|
||
</div>
|
||
|
||
{exerciseList.map((ex, idx) => (
|
||
<div key={idx} className="p-3.5 rounded-xl bg-slate-900 border border-slate-800 space-y-2 relative">
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||
<input
|
||
type="text"
|
||
required
|
||
placeholder="Exercise Name (e.g. Incline DB Press)"
|
||
value={ex.name}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].name = e.target.value;
|
||
setExerciseList(updated);
|
||
}}
|
||
className="px-3 py-1.5 rounded-lg bg-slate-950 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="Muscle Group (e.g. Chest)"
|
||
value={ex.muscleGroup}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].muscleGroup = e.target.value;
|
||
setExerciseList(updated);
|
||
}}
|
||
className="px-3 py-1.5 rounded-lg bg-slate-950 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-4 gap-2">
|
||
<div>
|
||
<label className="text-[10px] text-slate-400">Sets</label>
|
||
<input
|
||
type="number"
|
||
min="1"
|
||
max="10"
|
||
value={ex.setsCount}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].setsCount = Number(e.target.value);
|
||
setExerciseList(updated);
|
||
}}
|
||
className="w-full px-2 py-1 rounded bg-slate-950 border border-slate-800 text-xs text-slate-200 text-center"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-[10px] text-slate-400">Weight (kg)</label>
|
||
<input
|
||
type="number"
|
||
step="0.5"
|
||
value={ex.weight}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].weight = Number(e.target.value);
|
||
setExerciseList(updated);
|
||
}}
|
||
className="w-full px-2 py-1 rounded bg-slate-950 border border-slate-800 text-xs text-slate-200 text-center"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-[10px] text-slate-400">Reps</label>
|
||
<input
|
||
type="number"
|
||
min="1"
|
||
value={ex.reps}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].reps = Number(e.target.value);
|
||
setExerciseList(updated);
|
||
}}
|
||
className="w-full px-2 py-1 rounded bg-slate-950 border border-slate-800 text-xs text-slate-200 text-center"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="text-[10px] text-slate-400">RPE (6-10)</label>
|
||
<input
|
||
type="number"
|
||
step="0.5"
|
||
min="6"
|
||
max="10"
|
||
value={ex.rpe}
|
||
onChange={(e) => {
|
||
const updated = [...exerciseList];
|
||
updated[idx].rpe = Number(e.target.value);
|
||
setExerciseList(updated);
|
||
}}
|
||
className="w-full px-2 py-1 rounded bg-slate-950 border border-slate-800 text-xs text-slate-200 text-center"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{exerciseList.length > 1 && (
|
||
<button
|
||
type="button"
|
||
onClick={() => removeExerciseRow(idx)}
|
||
className="text-[11px] text-red-400 hover:text-red-300 pt-1"
|
||
>
|
||
Remove Exercise
|
||
</button>
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Scientific Notes & Biomechanics</label>
|
||
<textarea
|
||
rows={2}
|
||
placeholder="Tempo, lengthened position stretch, fatigue level..."
|
||
value={workoutNotes}
|
||
onChange={(e) => setWorkoutNotes(e.target.value)}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 resize-none focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="pt-3 border-t border-slate-800 flex justify-end gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => setIsWorkoutModalOpen(false)}
|
||
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-semibold text-slate-300 transition-colors"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="px-5 py-2 rounded-xl bg-gradient-to-r from-lime-500 to-emerald-500 text-slate-950 text-xs font-bold shadow-md shadow-lime-500/20 hover:scale-105 active:scale-95 transition-all cursor-pointer"
|
||
>
|
||
Save Workout Session
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* MODAL: ADJUST MACROS & BODYWEIGHT */}
|
||
{isNutritionModalOpen && (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm animate-in fade-in duration-200">
|
||
<div className="relative w-full max-w-md rounded-2xl bg-[#0e1424] border border-lime-500/30 p-6 shadow-2xl space-y-4">
|
||
<div className="flex items-center justify-between pb-3 border-b border-slate-800">
|
||
<h3 className="text-base font-bold text-white flex items-center gap-2">
|
||
<Scale className="w-4 h-4 text-lime-400" />
|
||
Adjust Daily Macros & Weight
|
||
</h3>
|
||
<button
|
||
onClick={() => setIsNutritionModalOpen(false)}
|
||
className="text-slate-400 hover:text-white p-1 rounded-lg hover:bg-slate-800"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<form onSubmit={handleSaveNutrition} className="space-y-4">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Bodyweight (kg)</label>
|
||
<input
|
||
type="number"
|
||
step="0.1"
|
||
required
|
||
value={editWeight}
|
||
onChange={(e) => setEditWeight(Number(e.target.value))}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Calories (kcal)</label>
|
||
<input
|
||
type="number"
|
||
required
|
||
value={editCalories}
|
||
onChange={(e) => setEditCalories(Number(e.target.value))}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Protein (g)</label>
|
||
<input
|
||
type="number"
|
||
required
|
||
value={editProtein}
|
||
onChange={(e) => setEditProtein(Number(e.target.value))}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Carbs (g)</label>
|
||
<input
|
||
type="number"
|
||
required
|
||
value={editCarbs}
|
||
onChange={(e) => setEditCarbs(Number(e.target.value))}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-xs font-semibold text-slate-300 mb-1">Fats (g)</label>
|
||
<input
|
||
type="number"
|
||
required
|
||
value={editFats}
|
||
onChange={(e) => setEditFats(Number(e.target.value))}
|
||
className="w-full px-3 py-2 rounded-xl bg-slate-900 border border-slate-800 text-xs text-slate-200 focus:outline-none focus:border-lime-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="pt-3 border-t border-slate-800 flex justify-end gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={() => setIsNutritionModalOpen(false)}
|
||
className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-semibold text-slate-300 transition-colors"
|
||
>
|
||
Cancel
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
className="px-5 py-2 rounded-xl bg-gradient-to-r from-lime-500 to-emerald-500 text-slate-950 text-xs font-bold shadow-md shadow-lime-500/20 hover:scale-105 active:scale-95 transition-all cursor-pointer"
|
||
>
|
||
Save Targets
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|