diff --git a/src/plays/body-composition-calculator/BodyCompositionCalculator.tsx b/src/plays/body-composition-calculator/BodyCompositionCalculator.tsx new file mode 100644 index 000000000..4c9df4642 --- /dev/null +++ b/src/plays/body-composition-calculator/BodyCompositionCalculator.tsx @@ -0,0 +1,82 @@ +import React, { useMemo, useState } from 'react'; + +import './styles/styles.css'; +import CalculatorForm from './components/CalculatorForm'; +import ResultDisplay from './components/ResultDisplay'; + +import { calculateBMR, calculateTDEE } from './utils/healthUtils'; +import { FormData } from './types'; + +const BodyCompositionCalculator: React.FC = () => { + const [formData, setFormData] = useState({ + gender: 'male', + weight: '', + height: '', + age: '', + activityLevel: 'sedentary' + }); + + /** + * Generic handler for updating form state + */ + const handleChange = (field: keyof FormData, value: string) => { + setFormData((prev) => ({ + ...prev, + [field]: value + })); + }; + + /** + * Convert Inputs Safely + */ + const numericWeight = Number(formData.weight); + const numericHeight = Number(formData.height); + const numericAge = Number(formData.age); + + const isValid = numericWeight > 0 && numericHeight > 0 && numericAge > 0; + + /** + * Derived BMR + */ + const bmr = useMemo(() => { + if (!isValid) return null; + + return calculateBMR({ + gender: formData.gender, + weight: numericWeight, + height: numericHeight, + age: numericAge + }); + }, [formData.gender, numericWeight, numericHeight, numericAge, isValid]); + + /** + * Derived TDEE + */ + const tdee = useMemo(() => { + if (!bmr) return null; + + return calculateTDEE(bmr, formData.activityLevel); + }, [bmr, formData.activityLevel]); + + return ( +
+

BMR & TDEE Calculator

+ +

+ BMR (Basal Metabolic Rate) is the number of calories your body needs at + complete rest to maintain essential functions such as breathing, circulation, and + temperature regulation. +
+
+ TDEE (Total Daily Energy Expenditure) is the total number of calories your + body burns in a day, including all physical activities like walking, exercise, and daily + tasks. +

+ + + +
+ ); +}; + +export default BodyCompositionCalculator; diff --git a/src/plays/body-composition-calculator/Readme.md b/src/plays/body-composition-calculator/Readme.md new file mode 100644 index 000000000..10ad2417f --- /dev/null +++ b/src/plays/body-composition-calculator/Readme.md @@ -0,0 +1,104 @@ +# Body Composition Calculator + +An interactive Body Composition Calculator that computes Basal Metabolic Rate (BMR) and Total Daily Energy Expenditure (TDEE) using scientifically validated formulas. + +This calculator helps users understand their daily calorie requirements based on body measurements and activity level. + +--- + +## Play Demographic + +- Language: TypeScript +- Level: Intermediate +- Category: Health / Fitness / Calculator + +--- + +## Creator Information + +- User: deansereigns +- GitHub Link: https://github.com/deansereigns + +--- + +## Implementation Details + +This play is built using React and TypeScript with a modular and scalable architecture. + +The calculator uses the **Mifflin-St Jeor Equation**, which is considered one of the most accurate formulas for estimating Basal Metabolic Rate. + +### BMR (Basal Metabolic Rate) + +BMR represents the number of calories your body requires to perform essential life-sustaining functions while at rest. + +These include: + +- Breathing +- Blood circulation +- Brain activity +- Cell production +- Maintaining body temperature + +Formula used: + +Male: + +```text +BMR = (10 × weight_kg) + (6.25 × height_cm) − (5 × age_years) + 5 +``` + +Female: + +```text +BMR = (10 × weight_kg) + (6.25 × height_cm) − (5 × age_years) − 161 +``` + +--- + +### TDEE (Total Daily Energy Expenditure) + +TDEE represents the total calories your body burns in a day including all physical activities. + +It is calculated by multiplying BMR with an activity multiplier. + +Formula: +```text +TDEE = BMR × activity_multiplier +``` + +Activity multipliers used: + +| Activity Level | Multiplier | +|---------------|------------| +| Sedentary | 1.2 | +| Light Activity | 1.375 | +| Moderate Activity | 1.55 | +| Active | 1.725 | +| Very Active | 1.9 | + +--- + +### Features Implemented + +- Built with TypeScript for type safety +- Modular and reusable component structure +- Real-time BMR and TDEE calculation using React Hooks +- Controlled form inputs with validation +- Clean and responsive user interface + +--- + +## Considerations + +- Validates inputs before performing calculations +- Handles empty or invalid values safely +- Optimized using React Hooks +- Easily extendable for additional health metrics + +--- + +## Future Improvements + +- BMI and body fat calculator +- Calorie recommendations for weight goals +- Enhanced UI and data visualization diff --git a/src/plays/body-composition-calculator/components/CalculatorForm.tsx b/src/plays/body-composition-calculator/components/CalculatorForm.tsx new file mode 100644 index 000000000..0c3f6411a --- /dev/null +++ b/src/plays/body-composition-calculator/components/CalculatorForm.tsx @@ -0,0 +1,104 @@ +import React, { ChangeEvent } from 'react'; +import { FormData, Gender, ActivityLevel } from '../types'; + +interface CalculatorFormProps { + formData: FormData; + onChange: (field: keyof FormData, value: string) => void; +} + +/** + * Calculator Form + * + * Responsible for rendering and managing all input fields. + * This is a presentational component with no business logic. + */ +const CalculatorForm: React.FC = ({ formData, onChange }) => { + /** + * Handle select change safely with typing + */ + const handleGenderChange = (event: ChangeEvent) => { + onChange('gender', event.target.value as Gender); + }; + + const handleActivityChange = (event: ChangeEvent) => { + onChange('activityLevel', event.target.value as ActivityLevel); + }; + + /** + * Handle numeric input changes + */ + const handleWeightChange = (event: ChangeEvent) => { + onChange('weight', event.target.value); + }; + + const handleHeightChange = (event: ChangeEvent) => { + onChange('height', event.target.value); + }; + + const handleAgeChange = (event: ChangeEvent) => { + onChange('age', event.target.value); + }; + + return ( +
+ {/* Gender*/} + + + {/* Weight*/} + + + {/* Height*/} + + + {/* Age*/} + + + {/* Activity Level */} + +
+ ); +}; + +export default CalculatorForm; diff --git a/src/plays/body-composition-calculator/components/ResultDisplay.tsx b/src/plays/body-composition-calculator/components/ResultDisplay.tsx new file mode 100644 index 000000000..56d1da934 --- /dev/null +++ b/src/plays/body-composition-calculator/components/ResultDisplay.tsx @@ -0,0 +1,45 @@ +import React from 'react'; + +interface ResultDisplayProps { + bmr: number | null; + tdee: number | null; +} + +/** + * ResultDisplay Component + * + * Displays calculated BMR and TDEE values. + * Pure Presentational component. + */ +const ResultDisplay: React.FC = ({ bmr, tdee }) => { + /** + * Format number Safely + */ + const formatValue = (value: number | null): string => { + if (value === null || Number.isNaN(value)) { + return '--'; + } + + return `${Math.round(value)} kcal/day`; + }; + + return ( +
+

Results

+ +
+ BMR + {formatValue(bmr)} +
+ +
+ TDEE + {formatValue(tdee)} +
+ + {bmr === null &&

Enter weight, height, and age to calculate.

} +
+ ); +}; + +export default ResultDisplay; diff --git a/src/plays/body-composition-calculator/styles/styles.css b/src/plays/body-composition-calculator/styles/styles.css new file mode 100644 index 000000000..53375d43c --- /dev/null +++ b/src/plays/body-composition-calculator/styles/styles.css @@ -0,0 +1,107 @@ +/* Container for the entire calculator */ +.container { + max-width: 800px; + margin: 0 auto; + padding: 16px; + border-radius: 8px; + background-color: #ffffff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + + display: flex; + flex-direction: column; + gap: 12px; +} + +/* Heading */ +.container h2 { + text-align: center; + margin-bottom: 8px; +} + +/* Form layout */ +.form { + display: flex; + flex-direction: column; + gap: 10px; +} + +/* Labels */ +.form label { + display: flex; + flex-direction: column; + font-size: 14px; + font-weight: 500; + gap: 4px; +} + +/* Inputes and selects */ +.form input, +.form select { + padding: 8px; + font-size: 14px; + border-radius: 4px; + border: 1px solid #ccc; + outline: none; +} + +/* Focus state */ +.form input:focus, +.form select:focus { + border-color: #007bff; +} + +/* Result Container */ +.result-container { + margin-top: 12px; + padding: 10px; + border-radius: 6px; + background-color: #f8f9fa; +} + +/* Individual result rows */ +.result-item { + display: flex; + justify-content: space-between; + font-size: 15px; + margin-bottom: 4px; +} + +/* Labels */ +.result-item .value { + font-weight: 600; +} + +/* Values */ +.result-item .value { + font-weight: 600; +} + +/* Hint Text */ +.hint { + font-size: 13px; + color: #666; + margin-top: 6px; +} + +.calculator-summary { + font-size: 14px; + color: #444; + margin-bottom: 18px; + line-height: 1.6; + background: #f4f7ff; + padding: 12px 16px; + border-radius: 8px; +} + +.calculator-title { + font-weight: 600; + text-align: center; + margin-bottom: 10px; + cursor: default; + transition: all 0.25s ease; +} + +.calculator-title:hover { + color: #2563eb; + transform: scale(1.03); +} diff --git a/src/plays/body-composition-calculator/types/index.ts b/src/plays/body-composition-calculator/types/index.ts new file mode 100644 index 000000000..aad7b4e3e --- /dev/null +++ b/src/plays/body-composition-calculator/types/index.ts @@ -0,0 +1,20 @@ +/** + * Gender options supported by calculator + */ +export type Gender = 'male' | 'female'; + +/** + * Activity levels used to calculate TDEE + */ +export type ActivityLevel = 'sedentary' | 'light' | 'moderate' | 'active' | 'very_active'; + +/** + * Form state structure + */ +export interface FormData { + gender: Gender; + weight: string; + height: string; + age: string; + activityLevel: ActivityLevel; +} diff --git a/src/plays/body-composition-calculator/utils/healthUtils.ts b/src/plays/body-composition-calculator/utils/healthUtils.ts new file mode 100644 index 000000000..72fe7a9e8 --- /dev/null +++ b/src/plays/body-composition-calculator/utils/healthUtils.ts @@ -0,0 +1,98 @@ +import { Gender, ActivityLevel } from '../types'; + +/** + * Parameters required for BMR calculation + */ +export interface BMRParams { + gender: Gender; + weight: number; + height: number; + age: number; +} + +/** + * Standard Activity multipliers used to convert BMR to TDEE + * Source: widely accepted fitness and nutrition standards + */ +const ACTIVITY_MULTIPLIERS: Record = { + sedentary: 1.2, + light: 1.375, + moderate: 1.55, + active: 1.725, + very_active: 1.9 +}; + +/** + * Calcualtes Basal Metabolic Rate (BMR) + * + * Formula: Mifflin-St jeor Equation + * + * Male: + * BMR = 10 x weight + 6.25 x height - 5 x age + 5 + * + * Female: + * BMR = 10 x weight + 6.25 x height -5 x age - 161 + * + * @params params - gender, weight (kg), height (cm), age(years) + * @returns number (kcal/day) + */ +export function calculateBMR(params: BMRParams): number { + const { gender, weight, height, age } = params; + + // Basic validation safeguard + if ( + weight <= 0 || + height <= 0 || + age <= 0 || + Number.isNaN(weight) || + Number.isNaN(height) || + Number.isNaN(age) + ) { + return 0; + } + if (gender === 'male') { + return 10 * weight + 6.25 * height - 5 * age + 5; + } + + return 10 * weight + 6.25 * height - 5 * age - 161; +} + +/** + * Calcualtes Total Daily Energy Expenditure (TDEE) + * + * Fromula: + * TDEE = BMR x activity multiplier + * + * @param bmr - Basal Metabolic Rate + * @param activityLevel - selected activity level + * @returns number (kcal/day) + */ +export function calculateTDEE(bmr: number, activityLevel: ActivityLevel): number { + if (bmr <= 0 || Number.isNaN(bmr)) { + return 0; + } + + const multiplier = ACTIVITY_MULTIPLIERS[activityLevel]; + + return bmr * multiplier; +} + +/** + * Optional helper function to calcualte both metrics together + * Useful if you want one call instead of two + */ +export function calculateHealthMetrics( + params: BMRParams, + activityLevel: ActivityLevel +): { + bmr: number; + tdee: number; +} { + const bmr = calculateBMR(params); + const tdee = calculateTDEE(bmr, activityLevel); + + return { + bmr, + tdee + }; +}