import { useEffect, useMemo, useState } from 'react'; import { createSession, deleteSession, loadSessions, updateSession, } from './utils/storage'; import { buildChartData, buildGradeProgressData, buildPainChartData, computeMaxRoutesThreshold, computeRecommendation, computeWeeklySummary, getWeekKey, groupByWeek, } from './utils/weekUtils'; import ClimbForm from './components/ClimbForm'; import WeeklySummary from './components/WeeklySummary'; import Charts from './components/Charts'; import SessionLog from './components/SessionLog'; import './App.css'; function App() { const [sessions, setSessions] = useState([]); const [isLoading, setIsLoading] = useState(true); const [syncError, setSyncError] = useState(''); useEffect(() => { let active = true; async function bootstrapSessions() { try { const loaded = await loadSessions(); if (active) { setSessions(Array.isArray(loaded) ? loaded : []); setSyncError(''); } } catch { if (active) { setSyncError('Could not load shared session data.'); } } finally { if (active) setIsLoading(false); } } bootstrapSessions(); return () => { active = false; }; }, []); const today = new Date().toISOString().split('T')[0]; const currentWeekKey = getWeekKey(today); const weeklyGroups = useMemo(() => groupByWeek(sessions), [sessions]); const chartData = useMemo(() => buildChartData(sessions), [sessions]); const gradeProgressData = useMemo(() => buildGradeProgressData(sessions), [sessions]); const painData = useMemo(() => buildPainChartData(sessions), [sessions]); const allWeekKeys = useMemo( () => [...new Set([...Object.keys(weeklyGroups), currentWeekKey])].sort(), [weeklyGroups, currentWeekKey] ); const [selectedWeekKey, setSelectedWeekKey] = useState(currentWeekKey); useEffect(() => { if (!allWeekKeys.includes(selectedWeekKey)) { setSelectedWeekKey(currentWeekKey); } }, [allWeekKeys, selectedWeekKey, currentWeekKey]); const selectedIdx = allWeekKeys.indexOf(selectedWeekKey); const hasPrevWeek = selectedIdx > 0; const hasNextWeek = selectedIdx >= 0 && selectedIdx < allWeekKeys.length - 1; const selectedWeekSessions = weeklyGroups[selectedWeekKey] || []; const selectedWeekSummary = computeWeeklySummary(selectedWeekSessions); const weeksBeforeSelected = allWeekKeys.slice(0, Math.max(selectedIdx + 1, 1)).slice(-4); const recentSummaries = weeksBeforeSelected.map((key) => computeWeeklySummary(weeklyGroups[key] || [])); const recommendation = computeRecommendation(recentSummaries); const maxRoutes = computeMaxRoutesThreshold(sessions, today); async function handleAddSession(newSession) { const sessionWithId = { ...newSession, id: crypto.randomUUID() }; try { const created = await createSession(sessionWithId); setSessions((prev) => [created, ...prev.filter((session) => session.id !== created.id)]); setSyncError(''); } catch { setSyncError('Could not save session.'); } } async function handleEditSession(id, updatedFields) { try { const updated = await updateSession(id, updatedFields); setSessions((prev) => prev.map((session) => (session.id === id ? updated : session))); setSyncError(''); } catch { setSyncError('Could not update session.'); } } async function handleDeleteSession(id) { try { await deleteSession(id); setSessions((prev) => prev.filter((session) => session.id !== id)); setSyncError(''); } catch { setSyncError('Could not delete session.'); } } return (

Climbing Dashboard

Log each climbing session and track routes, load, and RPE over time.

{syncError &&

{syncError}

}
{isLoading ? (

Loading shared sessions...

) : ( <>
hasPrevWeek && setSelectedWeekKey(allWeekKeys[selectedIdx - 1])} onNextWeek={() => hasNextWeek && setSelectedWeekKey(allWeekKeys[selectedIdx + 1])} />
)}
); } export default App;