File size: 4,539 Bytes
3d4aa49
e72c2c6
3d4aa49
 
ac30c1e
359f0ff
3d4aa49
 
 
 
 
dbc0348
 
 
e72c2c6
 
 
3d4aa49
 
e72c2c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d4aa49
 
 
 
 
 
7b6976e
 
 
 
 
 
 
 
 
 
 
 
 
 
49699c7
ac30c1e
359f0ff
49699c7
e72c2c6
 
 
 
 
 
 
 
 
 
3d4aa49
 
e72c2c6
 
 
 
 
 
 
 
 
 
49699c7
 
e72c2c6
 
 
 
 
 
 
 
3d4aa49
 
dbc0348
3d4aa49
 
 
e72c2c6
dbc0348
3d4aa49
e72c2c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d4aa49
dbc0348
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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;