Spaces:
Running
Running
| import { useState, useEffect } from 'react'; | |
| import { createRun, deleteRun, loadRuns, updateRun } from './utils/storage'; | |
| import { | |
| getWeekKey, groupByWeek, computeWeeklySummary, | |
| computeRecommendation, buildChartData, computeLongestRunThreshold, | |
| buildPainChartData, | |
| } from './utils/weekUtils'; | |
| import RunForm from './components/RunForm'; | |
| import WeeklySummary from './components/WeeklySummary'; | |
| import Charts from './components/Charts'; | |
| import RunLog from './components/RunLog'; | |
| import './App.css'; | |
| function App() { | |
| const [runs, setRuns] = useState([]); | |
| const [isLoading, setIsLoading] = useState(true); | |
| const [syncError, setSyncError] = useState(''); | |
| useEffect(() => { | |
| let active = true; | |
| async function bootstrapRuns() { | |
| try { | |
| const loaded = await loadRuns(); | |
| if (active) { | |
| setRuns(Array.isArray(loaded) ? loaded : []); | |
| setSyncError(''); | |
| } | |
| } catch { | |
| if (active) { | |
| setSyncError('Could not load shared run data.'); | |
| } | |
| } finally { | |
| if (active) setIsLoading(false); | |
| } | |
| } | |
| bootstrapRuns(); | |
| return () => { | |
| active = false; | |
| }; | |
| }, []); | |
| const today = new Date().toISOString().split('T')[0]; | |
| const currentWeekKey = getWeekKey(today); | |
| const weeklyGroups = groupByWeek(runs); | |
| const chartData = buildChartData(runs); | |
| // Week navigation: all weeks that have data, plus the current week | |
| const allWeekKeys = [...new Set([...Object.keys(weeklyGroups), currentWeekKey])].sort(); | |
| const [selectedWeekKey, setSelectedWeekKey] = useState(currentWeekKey); | |
| const selectedIdx = allWeekKeys.indexOf(selectedWeekKey); | |
| const hasPrevWeek = selectedIdx > 0; | |
| const hasNextWeek = selectedIdx < allWeekKeys.length - 1; | |
| const selectedWeekRuns = weeklyGroups[selectedWeekKey] || []; | |
| const selectedWeekSummary = computeWeeklySummary(selectedWeekRuns); | |
| // ACWR: get up to 4 weeks before the selected week for recommendation | |
| const weeksBeforeSelected = allWeekKeys.slice(0, selectedIdx + 1).slice(-4); | |
| const recentSummaries = weeksBeforeSelected.map((key) => computeWeeklySummary(weeklyGroups[key] || [])); | |
| const recommendation = computeRecommendation(recentSummaries); | |
| const longestRunData = computeLongestRunThreshold(runs, today); | |
| const painChartData = buildPainChartData(runs); | |
| async function handleAddRun(newRun) { | |
| const runWithId = { ...newRun, id: crypto.randomUUID() }; | |
| try { | |
| const created = await createRun(runWithId); | |
| setRuns((prev) => [created, ...prev.filter((run) => run.id !== created.id)]); | |
| setSyncError(''); | |
| } catch { | |
| setSyncError('Could not save run.'); | |
| } | |
| } | |
| async function handleEditRun(id, updatedFields) { | |
| try { | |
| const updated = await updateRun(id, updatedFields); | |
| setRuns((prev) => | |
| prev.map((r) => (r.id === id ? updated : r)) | |
| ); | |
| setSyncError(''); | |
| } catch { | |
| setSyncError('Could not update run.'); | |
| } | |
| } | |
| async function handleDeleteRun(id) { | |
| try { | |
| await deleteRun(id); | |
| setRuns((prev) => prev.filter((r) => r.id !== id)); | |
| setSyncError(''); | |
| } catch { | |
| setSyncError('Could not delete run.'); | |
| } | |
| } | |
| return ( | |
| <div className="dashboard"> | |
| <header className="dashboard-header"> | |
| <h1>Running Dashboard</h1> | |
| {syncError && <p className="sync-error">{syncError}</p>} | |
| </header> | |
| <main className="dashboard-main"> | |
| {isLoading ? ( | |
| <p className="loading-message">Loading shared runs...</p> | |
| ) : ( | |
| <> | |
| <div className="dashboard-top"> | |
| <RunForm onAddRun={handleAddRun} /> | |
| <WeeklySummary | |
| summary={selectedWeekSummary} | |
| recommendation={recommendation} | |
| weekKey={selectedWeekKey} | |
| isCurrentWeek={selectedWeekKey === currentWeekKey} | |
| weeksOfData={weeksBeforeSelected.length} | |
| longestRun={longestRunData} | |
| hasPrevWeek={hasPrevWeek} | |
| hasNextWeek={hasNextWeek} | |
| onPrevWeek={() => hasPrevWeek && setSelectedWeekKey(allWeekKeys[selectedIdx - 1])} | |
| onNextWeek={() => hasNextWeek && setSelectedWeekKey(allWeekKeys[selectedIdx + 1])} | |
| /> | |
| </div> | |
| <Charts data={chartData} painData={painChartData} /> | |
| <RunLog runs={runs} onEditRun={handleEditRun} onDeleteRun={handleDeleteRun} /> | |
| </> | |
| )} | |
| </main> | |
| </div> | |
| ); | |
| } | |
| export default App; | |