import { useState } from 'react'; import { formatPace } from '../utils/weekUtils'; import './RunLog.css'; const INJURY_LOCS = [ { key: 'left_knee', label: 'L Knee' }, { key: 'right_knee', label: 'R Knee' }, ]; function RunLog({ runs, onEditRun, onDeleteRun }) { const [editingId, setEditingId] = useState(null); const [editForm, setEditForm] = useState({}); if (!runs || runs.length === 0) { return (

Run History

No runs logged yet. Add your first run above.

); } const sorted = [...runs].sort((a, b) => b.date.localeCompare(a.date)); function formatDate(dateStr) { const d = new Date(dateStr + 'T00:00:00'); return d.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); } function handleEdit(run) { setEditingId(run.id); const injuries = {}; for (const loc of INJURY_LOCS) { const d = run[`${loc.key}_during`]; const a = run[`${loc.key}_after`]; // Also support legacy single-location format const legacyMatch = run.injury_location === loc.key; if (d != null || a != null || legacyMatch) { injuries[loc.key] = { enabled: true, during: legacyMatch && d == null ? (run.pain_during ?? '') : (d ?? ''), after: legacyMatch && a == null ? (run.pain_after ?? '') : (a ?? ''), }; } } setEditForm({ date: run.date, distance_km: run.distance_km, time_minutes: run.time_minutes, rpe: run.rpe, notes: run.notes || '', injuries, }); } function toggleEditInjury(locKey, checked) { setEditForm((prev) => { const injuries = { ...prev.injuries }; if (checked) { injuries[locKey] = { enabled: true, during: '', after: '' }; } else { delete injuries[locKey]; } return { ...prev, injuries }; }); } function updateEditInjury(locKey, field, value) { setEditForm((prev) => ({ ...prev, injuries: { ...prev.injuries, [locKey]: { ...prev.injuries[locKey], [field]: value }, }, })); } function handleSave() { const dist = parseFloat(editForm.distance_km); const mins = parseFloat(editForm.time_minutes); const rpe = parseInt(editForm.rpe, 10); if (!editForm.date || isNaN(dist) || dist <= 0 || isNaN(mins) || mins <= 0 || isNaN(rpe) || rpe < 1 || rpe > 10) return; const updated = { date: editForm.date, distance_km: dist, time_minutes: mins, rpe, notes: (editForm.notes || '').trim(), // Clear legacy fields injury_location: null, pain_during: null, pain_after: null, }; for (const loc of INJURY_LOCS) { const injury = editForm.injuries?.[loc.key]; if (injury?.enabled) { updated[`${loc.key}_during`] = injury.during !== '' ? Number(injury.during) : null; updated[`${loc.key}_after`] = injury.after !== '' ? Number(injury.after) : null; } else { updated[`${loc.key}_during`] = null; updated[`${loc.key}_after`] = null; } } onEditRun(editingId, updated); setEditingId(null); } function handleCancel() { setEditingId(null); } function handleDelete(id) { if (window.confirm('Delete this run?')) { onDeleteRun(id); } } function handleKeyDown(e) { if (e.key === 'Enter') handleSave(); if (e.key === 'Escape') handleCancel(); } return (

Run History

{sorted.map((run) => editingId === run.id ? ( ) : ( ) )}
Date Distance Time Pace RPE Load Pain (D/A) Notes
setEditForm({ ...editForm, date: e.target.value })} onKeyDown={handleKeyDown} /> setEditForm({ ...editForm, distance_km: e.target.value })} onKeyDown={handleKeyDown} /> setEditForm({ ...editForm, time_minutes: e.target.value })} onKeyDown={handleKeyDown} /> {formatPace(parseFloat(editForm.time_minutes), parseFloat(editForm.distance_km))}/km setEditForm({ ...editForm, rpe: e.target.value })} onKeyDown={handleKeyDown} /> {(parseFloat(editForm.distance_km || 0) * parseInt(editForm.rpe || 0, 10)).toFixed(0)}
{INJURY_LOCS.map((loc) => { const injury = editForm.injuries?.[loc.key]; const enabled = !!injury?.enabled; return (
{enabled && (
updateEditInjury(loc.key, 'during', e.target.value)} onKeyDown={handleKeyDown} placeholder="D" className="pain-input" /> / updateEditInjury(loc.key, 'after', e.target.value)} onKeyDown={handleKeyDown} placeholder="A" className="pain-input" />
)}
); })}
setEditForm({ ...editForm, notes: e.target.value })} onKeyDown={handleKeyDown} placeholder="Notes..." />
{formatDate(run.date)} {run.distance_km.toFixed(1)} km {run.time_minutes} min {formatPace(run.time_minutes, run.distance_km)}/km {run.rpe}/10 {(run.distance_km * run.rpe).toFixed(0)} {(() => { const entries = INJURY_LOCS.filter((loc) => { // Support new per-location fields and legacy single-location format const hasNew = run[`${loc.key}_during`] != null || run[`${loc.key}_after`] != null; const hasLegacy = run.injury_location === loc.key; return hasNew || hasLegacy; }); if (entries.length === 0) return '–'; return entries.map((loc) => { const d = run[`${loc.key}_during`] ?? (run.injury_location === loc.key ? run.pain_during : null); const a = run[`${loc.key}_after`] ?? (run.injury_location === loc.key ? run.pain_after : null); return
{loc.label}: {d ?? '–'}/{a ?? '–'}
; }); })()}
{run.notes || ''}
); } export default RunLog;