import { useMemo, useState } from 'react'; import { INJURY_TRACKERS, RPE_CRITERIA, TRACKED_GRADE_SCALE, capGradeAtMax, getGradeScore, } from '../utils/weekUtils'; import './ClimbForm.css'; const SESSION_TYPE_OPTIONS = [ { value: 'lead', label: 'Lead' }, { value: 'bouldering', label: 'Bouldering' }, ]; function buildEmptyPainInputs() { return Object.fromEntries( INJURY_TRACKERS.flatMap((tracker) => [ [`${tracker.key}_during`, ''], [`${tracker.key}_after`, ''], ]) ); } function ClimbForm({ onAddSession }) { const today = new Date().toISOString().split('T')[0]; const [date, setDate] = useState(today); const [sessionType, setSessionType] = useState('bouldering'); const [routesCount, setRoutesCount] = useState(''); const [maxGrade, setMaxGrade] = useState(''); const [rpe, setRpe] = useState(5); const [notes, setNotes] = useState(''); const [painInputs, setPainInputs] = useState(() => buildEmptyPainInputs()); const selectedCriteria = useMemo(() => RPE_CRITERIA[rpe] || RPE_CRITERIA[5], [rpe]); function handleSubmit(event) { event.preventDefault(); const routes = parseInt(routesCount, 10); const parsedRpe = parseInt(rpe, 10); const normalizedMaxGrade = capGradeAtMax(maxGrade); const maxGradeScore = getGradeScore(normalizedMaxGrade); const leftElbowDuring = painInputs.left_elbow_during === '' ? null : Number(painInputs.left_elbow_during); const leftElbowAfter = painInputs.left_elbow_after === '' ? null : Number(painInputs.left_elbow_after); const rightShoulderDuring = painInputs.right_shoulder_during === '' ? null : Number(painInputs.right_shoulder_during); const rightShoulderAfter = painInputs.right_shoulder_after === '' ? null : Number(painInputs.right_shoulder_after); if ( !date || Number.isNaN(routes) || routes <= 0 || Number.isNaN(parsedRpe) || parsedRpe < 1 || parsedRpe > 10 || !normalizedMaxGrade || maxGradeScore == null || (leftElbowDuring != null && (Number.isNaN(leftElbowDuring) || leftElbowDuring < 0 || leftElbowDuring > 10)) || (leftElbowAfter != null && (Number.isNaN(leftElbowAfter) || leftElbowAfter < 0 || leftElbowAfter > 10)) || (rightShoulderDuring != null && (Number.isNaN(rightShoulderDuring) || rightShoulderDuring < 0 || rightShoulderDuring > 10)) || (rightShoulderAfter != null && (Number.isNaN(rightShoulderAfter) || rightShoulderAfter < 0 || rightShoulderAfter > 10)) ) { return; } onAddSession({ date, session_type: sessionType, routes_count: routes, max_grade: normalizedMaxGrade, rpe: parsedRpe, left_elbow_during: leftElbowDuring, left_elbow_after: leftElbowAfter, right_shoulder_during: rightShoulderDuring, right_shoulder_after: rightShoulderAfter, notes: notes.trim(), }); setRoutesCount(''); setMaxGrade(''); setRpe(5); setNotes(''); setPainInputs(buildEmptyPainInputs()); } return (

Log a Session

setDate(event.target.value)} required />
setRoutesCount(event.target.value)} required />
setMaxGrade(event.target.value)} onBlur={() => setMaxGrade((prev) => capGradeAtMax(prev))} list="grade-scale-list" required /> {TRACKED_GRADE_SCALE.map((grade) => (
setRpe(Number(event.target.value))} />
Very easy Absolute limit
{selectedCriteria.intensity}
Grades {selectedCriteria.grades} Pump {selectedCriteria.pump_level || '—'} Suggested Session {selectedCriteria.suggested_session}