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;