all repos — go-lift @ 387721fc5e90ba268efbae885c9baf8e0a543f28

Lightweight workout tracker prototype..

ui/src/pages/HomePage.tsx (view raw)

  1import { useEffect, useState } from 'react';
  2import { FaCalendarCheck, FaClock, FaDumbbell } from 'react-icons/fa';
  3import type { RecordRoutine, WorkoutStats } from '../types/models';
  4import { WorkoutService } from '../services/api';
  5
  6const HomePage = () => {
  7  const [stats, setStats] = useState<WorkoutStats | null>(null);
  8  const [loading, setLoading] = useState<boolean>(true);
  9  const [error, setError] = useState<string | null>(null);
 10
 11  useEffect(() => {
 12    const fetchStats = async () => {
 13      try {
 14        setLoading(true);
 15        const data = await WorkoutService.getStats();
 16        setStats(data);
 17        setError(null);
 18      } catch (err) {
 19        console.error('Failed to fetch workout stats:', err);
 20        setError('Could not load workout statistics. Please try again later.');
 21      } finally {
 22        setLoading(false);
 23      }
 24    };
 25
 26    fetchStats();
 27  }, []);
 28
 29  const formatDate = (dateStr: string) => {
 30    const date = new Date(dateStr);
 31    return new Intl.DateTimeFormat('en-US', {
 32      weekday: 'short',
 33      month: 'short',
 34      day: 'numeric',
 35      hour: '2-digit',
 36      minute: '2-digit'
 37    }).format(date);
 38  };
 39
 40  const calculateDuration = (start: string, end: string) => {
 41    const startTime = new Date(start).getTime();
 42    const endTime = new Date(end).getTime();
 43    const durationMinutes = Math.round((endTime - startTime) / (1000 * 60));
 44    
 45    return `${durationMinutes} min`;
 46  };
 47
 48  if (loading) {
 49    return (
 50      <div className="page home-page">
 51        <h1>Home</h1>
 52        <div className="loading">Loading workout data...</div>
 53      </div>
 54    );
 55  }
 56
 57  if (error) {
 58    return (
 59      <div className="page home-page">
 60        <h1>Home</h1>
 61        <div className="error-message">{error}</div>
 62      </div>
 63    );
 64  }
 65
 66  // Display placeholder if no stats
 67  if (!stats) {
 68    return (
 69      <div className="page home-page">
 70        <h1>Workout Overview</h1>
 71        <div className="card">
 72          <h2>Welcome to Go Lift!</h2>
 73          <p>Start by adding exercises and creating your first workout routine.</p>
 74          <div className="mt-lg">
 75            <a href="/workouts" className="btn btn-primary">Go to Workouts</a>
 76          </div>
 77        </div>
 78      </div>
 79    );
 80  }
 81
 82  return (
 83    <div className="page home-page">
 84      <h1>Workout Overview</h1>
 85      
 86      {/* Statistics Cards */}
 87      <div className="stats-grid">
 88        <div className="card stat-card">
 89          <div className="stat-icon">
 90            <FaCalendarCheck size={24} />
 91          </div>
 92          <div className="stat-content">
 93            <div className="stat-value">{stats.totalWorkouts}</div>
 94            <div className="stat-label">Total Workouts</div>
 95          </div>
 96        </div>
 97        
 98        <div className="card stat-card">
 99          <div className="stat-icon">
100            <FaClock size={24} />
101          </div>
102          <div className="stat-content">
103            <div className="stat-value">{stats.totalMinutes}</div>
104            <div className="stat-label">Total Minutes</div>
105          </div>
106        </div>
107        
108        <div className="card stat-card">
109          <div className="stat-icon">
110            <FaDumbbell size={24} />
111          </div>
112          <div className="stat-content">
113            <div className="stat-value">{stats.totalExercises}</div>
114            <div className="stat-label">Exercises Done</div>
115          </div>
116        </div>
117      </div>
118
119      {/* Favorite Data */}
120      {(stats.mostFrequentExercise || stats.mostFrequentRoutine) && (
121        <div className="card mb-lg">
122          <h2>Your Favorites</h2>
123          {stats.mostFrequentRoutine && (
124            <div className="favorite-item">
125              <div className="favorite-label">Most Used Routine:</div>
126              <div className="favorite-value">{stats.mostFrequentRoutine.name} ({stats.mostFrequentRoutine.count}x)</div>
127            </div>
128          )}
129          {stats.mostFrequentExercise && (
130            <div className="favorite-item">
131              <div className="favorite-label">Most Performed Exercise:</div>
132              <div className="favorite-value">{stats.mostFrequentExercise.name} ({stats.mostFrequentExercise.count}x)</div>
133            </div>
134          )}
135        </div>
136      )}
137
138      {/* Recent Workouts */}
139      <h2>Recent Workouts</h2>
140      {stats.recentWorkouts && stats.recentWorkouts.length > 0 ? (
141        stats.recentWorkouts.map((workout: RecordRoutine) => (
142          <div key={workout.id} className="card workout-card">
143            <div className="workout-header">
144              <h3>{workout.routine?.name || 'Workout'}</h3>
145              <div className="workout-date">{formatDate(workout.startedAt)}</div>
146            </div>
147            {workout.endedAt && (
148              <div className="workout-duration">
149                Duration: {calculateDuration(workout.startedAt, workout.endedAt)}
150              </div>
151            )}
152            <div className="workout-exercises">
153            </div>
154          </div>
155        ))
156      ) : (
157        <div className="empty-state">
158          <p>No workouts recorded yet. Start your fitness journey today!</p>
159          <a href="/new-workout" className="btn btn-primary mt-md">Record a Workout</a>
160        </div>
161      )}
162      
163      <style>{`
164        .stats-grid {
165          display: grid;
166          grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
167          gap: var(--spacing-md);
168          margin-bottom: var(--spacing-lg);
169        }
170        
171        .stat-card {
172          display: flex;
173          align-items: center;
174        }
175        
176        .stat-icon {
177          display: flex;
178          align-items: center;
179          justify-content: center;
180          width: 50px;
181          height: 50px;
182          background-color: rgba(0, 122, 255, 0.1);
183          border-radius: 50%;
184          color: var(--primary-color);
185          margin-right: var(--spacing-md);
186        }
187        
188        .stat-value {
189          font-size: 24px;
190          font-weight: bold;
191        }
192        
193        .stat-label {
194          color: var(--dark-gray);
195        }
196        
197        .favorite-item {
198          display: flex;
199          justify-content: space-between;
200          padding: var(--spacing-sm) 0;
201          border-bottom: 1px solid var(--light-gray);
202        }
203        
204        .favorite-item:last-child {
205          border-bottom: none;
206        }
207        
208        .favorite-label {
209          color: var(--dark-gray);
210        }
211        
212        .workout-card {
213          margin-bottom: var(--spacing-md);
214        }
215        
216        .workout-header {
217          display: flex;
218          justify-content: space-between;
219          align-items: center;
220          margin-bottom: var(--spacing-sm);
221        }
222        
223        .workout-date {
224          color: var(--dark-gray);
225          font-size: 0.9rem;
226        }
227        
228        .workout-duration, .workout-exercises, .workout-notes {
229          margin-bottom: var(--spacing-sm);
230        }
231        
232        .workout-feeling {
233          color: var(--warning-color);
234        }
235        
236        .empty-state {
237          text-align: center;
238          padding: var(--spacing-xl);
239        }
240      `}</style>
241    </div>
242  );
243};
244
245export default HomePage;