lewtun's picture
lewtun HF Staff
Add shared backend for running dashboard
e72c2c6
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;