ui/src/pages/NewWorkoutPage.tsx (view raw)
1import { useState, useEffect } from 'react';
2import { useNavigate, useLocation } from 'react-router-dom';
3import {
4 FaArrowLeft,
5 FaCheck,
6 FaSave,
7 FaPlay,
8 FaStop,
9 FaStar,
10 FaRegStar,
11 FaForward
12} from 'react-icons/fa';
13import type {
14 Routine,
15 Exercise,
16 RecordRoutine,
17 RecordSet,
18 Set,
19 RecordItem
20} from '../types/models';
21import { routineService } from '../services/api';
22
23interface SetForWorkout {
24 id?: number;
25 setId: number;
26 actualReps: number;
27 actualWeight: number;
28 actualDuration: number;
29 originalSet: Set;
30 completed: boolean;
31}
32
33interface ExerciseForWorkout {
34 id?: number;
35 exerciseId: number;
36 exercise: Exercise;
37 sets: SetForWorkout[];
38 startedAt?: string;
39 endedAt?: string;
40 actualRestTime: number; // in seconds
41 notes: string;
42}
43
44const NewWorkoutPage = () => {
45 const navigate = useNavigate();
46 const location = useLocation();
47
48 // Routines state
49 const [routines, setRoutines] = useState<Routine[]>([]);
50 const [selectedRoutine, setSelectedRoutine] = useState<Routine | null>(null);
51 const [isLoading, setIsLoading] = useState<boolean>(true);
52 const [error, setError] = useState<string | null>(null);
53
54 // Workout tracking state
55 const [workoutStarted, setWorkoutStarted] = useState<boolean>(false);
56 const [workoutCompleted, setWorkoutCompleted] = useState<boolean>(false);
57 const [startTime, setStartTime] = useState<string>('');
58 const [endTime, setEndTime] = useState<string | null>(null);
59 const [elapsedSeconds, setElapsedSeconds] = useState<number>(0);
60 const [intervalId, setIntervalId] = useState<number | null>(null);
61
62 // Exercise tracking state
63 const [currentExerciseIndex, setCurrentExerciseIndex] = useState<number>(0);
64 const [workoutExercises, setWorkoutExercises] = useState<ExerciseForWorkout[]>([]);
65
66 // Workout notes and rating
67 const [workoutNotes, setWorkoutNotes] = useState<string>('');
68 const [feelingRating, setFeelingRating] = useState<number>(3);
69
70 // Success message state
71 const [successMessage, setSuccessMessage] = useState<string | null>(null);
72
73 // Load routines and check for pre-selected routine
74 useEffect(() => {
75 const fetchRoutines = async () => {
76 try {
77 setIsLoading(true);
78 const data = await routineService.getAll();
79 setRoutines(data);
80
81 // Check if a routine was pre-selected (from workouts page)
82 if (location.state && location.state.routineId) {
83 const routineId = location.state.routineId;
84 const routine = data.find(r => r.id === routineId);
85
86 if (routine) {
87 handleSelectRoutine(routine);
88 }
89 }
90
91 setError(null);
92 } catch (err) {
93 console.error('Failed to fetch routines:', err);
94 setError('Could not load workout routines. Please try again later.');
95 } finally {
96 setIsLoading(false);
97 }
98 };
99
100 fetchRoutines();
101 }, [location]);
102
103 // Setup the workout when a routine is selected
104 const handleSelectRoutine = (routine: Routine) => {
105 setSelectedRoutine(routine);
106
107 // Initialize workout exercises from routine items
108 const exercises: ExerciseForWorkout[] = [];
109
110 // Process routine items into exercises for the workout
111 routine.items.forEach(item => {
112 if (item.exerciseItems && item.exerciseId) {
113 // This is a regular exercise item
114 const exercise = item.exercise;
115
116 // Get the sets from the exercise or create default ones
117 const exerciseSets = exercise.sets || [];
118 const setsForWorkout: SetForWorkout[] = exerciseSets.map(set => ({
119 setId: set.id || 0,
120 originalSet: set,
121 actualReps: set.reps,
122 actualWeight: set.weight,
123 actualDuration: set.duration,
124 completed: false
125 }));
126
127 // If there are no sets defined, create a default set
128 if (setsForWorkout.length === 0) {
129 setsForWorkout.push({
130 setId: 0,
131 originalSet: {
132 id: 0,
133 exerciseId: exercise.id || 0,
134 reps: 10,
135 weight: 0,
136 duration: 0,
137 orderIndex: 0
138 },
139 actualReps: 10,
140 actualWeight: 0,
141 actualDuration: 0,
142 completed: false
143 });
144 }
145
146 exercises.push({
147 exerciseId: exercise.id || 0,
148 exercise: exercise,
149 sets: setsForWorkout,
150 actualRestTime: item.restTime,
151 notes: ''
152 });
153 }
154 // We could handle supersets here if needed
155 });
156
157 setWorkoutExercises(exercises);
158 setCurrentExerciseIndex(0);
159 };
160
161 // Start the workout
162 const startWorkout = () => {
163 if (!selectedRoutine) return;
164
165 const now = new Date().toISOString();
166 setStartTime(now);
167 setWorkoutStarted(true);
168
169 // Mark first exercise as started
170 if (workoutExercises.length > 0) {
171 const updatedExercises = [...workoutExercises];
172 updatedExercises[0].startedAt = now;
173 setWorkoutExercises(updatedExercises);
174 }
175
176 // Start the timer
177 const id = window.setInterval(() => {
178 setElapsedSeconds(prev => prev + 1);
179 }, 1000);
180
181 setIntervalId(id);
182 };
183
184 // Complete the workout
185 const completeWorkout = () => {
186 if (intervalId) {
187 clearInterval(intervalId);
188 setIntervalId(null);
189 }
190
191 const now = new Date().toISOString();
192 setEndTime(now);
193
194 // Mark current exercise as completed if not already
195 if (workoutExercises.length > 0 && currentExerciseIndex < workoutExercises.length) {
196 const updatedExercises = [...workoutExercises];
197 const currentExercise = updatedExercises[currentExerciseIndex];
198
199 if (currentExercise.startedAt && !currentExercise.endedAt) {
200 currentExercise.endedAt = now;
201 }
202
203 setWorkoutExercises(updatedExercises);
204 }
205
206 setWorkoutCompleted(true);
207 };
208
209 // Format timer display
210 const formatTime = (seconds: number) => {
211 const hrs = Math.floor(seconds / 3600);
212 const mins = Math.floor((seconds % 3600) / 60);
213 const secs = seconds % 60;
214
215 return `${hrs > 0 ? hrs + ':' : ''}${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
216 };
217
218 // Handle set completion toggle
219 const toggleSetCompleted = (exerciseIndex: number, setIndex: number) => {
220 const updatedExercises = [...workoutExercises];
221 const currentSet = updatedExercises[exerciseIndex].sets[setIndex];
222 currentSet.completed = !currentSet.completed;
223
224 setWorkoutExercises(updatedExercises);
225 };
226
227 // Handle weight, reps or duration change
228 const handleSetDataChange = (
229 exerciseIndex: number,
230 setIndex: number,
231 field: 'actualReps' | 'actualWeight' | 'actualDuration',
232 value: number
233 ) => {
234 const updatedExercises = [...workoutExercises];
235 const currentSet = updatedExercises[exerciseIndex].sets[setIndex];
236 currentSet[field] = value;
237
238 setWorkoutExercises(updatedExercises);
239 };
240
241 // Move to next exercise
242 const nextExercise = () => {
243 if (currentExerciseIndex >= workoutExercises.length - 1) return;
244
245 const now = new Date().toISOString();
246 const updatedExercises = [...workoutExercises];
247
248 // Complete current exercise
249 const currentExercise = updatedExercises[currentExerciseIndex];
250 if (currentExercise.startedAt && !currentExercise.endedAt) {
251 currentExercise.endedAt = now;
252 }
253
254 // Start next exercise
255 const nextIndex = currentExerciseIndex + 1;
256 const nextExercise = updatedExercises[nextIndex];
257 nextExercise.startedAt = now;
258
259 setWorkoutExercises(updatedExercises);
260 setCurrentExerciseIndex(nextIndex);
261 };
262
263 // Handle notes for an exercise
264 const handleExerciseNotes = (exerciseIndex: number, notes: string) => {
265 const updatedExercises = [...workoutExercises];
266 updatedExercises[exerciseIndex].notes = notes;
267
268 setWorkoutExercises(updatedExercises);
269 };
270
271 // Create RecordSets from workout exercise sets
272 const createRecordSets = (exercise: ExerciseForWorkout): RecordSet[] => {
273 return exercise.sets.map((set, index) => ({
274 recordExerciseId: 0, // Will be filled in by backend
275 setId: set.setId,
276 actualReps: set.actualReps,
277 actualWeight: set.actualWeight,
278 actualDuration: set.actualDuration,
279 completedAt: exercise.endedAt || new Date().toISOString(),
280 orderIndex: index,
281 set: set.originalSet
282 }));
283 };
284
285 // Save workout record
286 const saveWorkout = async () => {
287 if (!selectedRoutine || !startTime) return;
288
289 try {
290 const now = new Date().toISOString();
291
292 // Ensure all exercises have start/end times
293 const completedExercises = workoutExercises.map((ex) => {
294 if (!ex.startedAt) {
295 ex.startedAt = startTime;
296 }
297 if (!ex.endedAt) {
298 ex.endedAt = endTime || now;
299 }
300 return ex;
301 });
302
303 // Create RecordExercises from completed exercises
304 const recordExercises: RecordExercise[] = completedExercises.map((ex, index) => ({
305 id: undefined,
306 recordRoutineId: 0, // Will be filled in by backend
307 exerciseId: ex.exerciseId,
308 startedAt: ex.startedAt || startTime,
309 endedAt: ex.endedAt || now,
310 actualRestTime: ex.actualRestTime,
311 orderIndex: index,
312 recordSets: createRecordSets(ex),
313 exercise: ex.exercise
314 }));
315
316 // Create RecordRoutineItems from recordExercises
317 const recordItems: RecordItem[] = recordExercises.map((ex, index) => ({
318 recordRoutineId: 0, // Will be filled in by backend
319 recordExerciseId: undefined, // Will be filled in after recordExercise is created
320 recordSuperSetId: null,
321 actualRestTime: workoutExercises[index].actualRestTime,
322 orderIndex: index,
323 recordExercise: ex,
324 recordSuperSet: null
325 }));
326
327 const workoutRecord: RecordRoutine = {
328 routineId: selectedRoutine.id!,
329 duration: elapsedSeconds,
330 routine: selectedRoutine,
331 recordItems: recordItems
332 };
333
334 await WorkoutService.create(workoutRecord);
335 setSuccessMessage('Workout saved successfully!');
336
337 // Redirect after a brief delay
338 setTimeout(() => {
339 navigate('/home');
340 }, 1500);
341 } catch (err) {
342 console.error('Failed to save workout:', err);
343 setError('Failed to save your workout. Please try again.');
344 }
345 };
346
347 // Check if all sets in current exercise are completed
348 const isCurrentExerciseComplete = () => {
349 if (currentExerciseIndex >= workoutExercises.length) return false;
350
351 const currentExercise = workoutExercises[currentExerciseIndex];
352 return currentExercise.sets.every(set => set.completed);
353 };
354
355 // Progress status percentage
356 const calculateProgress = () => {
357 if (workoutExercises.length === 0) return 0;
358
359 const totalSets = workoutExercises.reduce((total, ex) => total + ex.sets.length, 0);
360 const completedSets = workoutExercises.reduce((total, ex) => {
361 return total + ex.sets.filter(set => set.completed).length;
362 }, 0);
363
364 return Math.round((completedSets / totalSets) * 100);
365 };
366
367 return (
368 <div className="page new-workout-page">
369 <div className="page-header">
370 <button
371 onClick={() => navigate(-1)}
372 className="btn btn-secondary back-button"
373 >
374 <FaArrowLeft /> Back
375 </button>
376 <h1>New Workout</h1>
377 </div>
378
379 {error && <div className="error-message">{error}</div>}
380 {successMessage && <div className="success-message">{successMessage}</div>}
381
382 {!selectedRoutine ? (
383 // Routine selection view
384 <div className="routine-selection card">
385 <h2>Select a Routine</h2>
386
387 {isLoading ? (
388 <div className="loading">Loading routines...</div>
389 ) : routines.length === 0 ? (
390 <div className="empty-state">
391 <p>No routines found.</p>
392 <p>Create a routine to start working out.</p>
393 <button
394 onClick={() => navigate('/new-routine')}
395 className="btn btn-primary mt-md"
396 >
397 Create Routine
398 </button>
399 </div>
400 ) : (
401 <div className="routines-list">
402 {routines.map(routine => (
403 <div key={routine.id} className="routine-item" onClick={() => handleSelectRoutine(routine)}>
404 <h3>{routine.name}</h3>
405 {routine.description && <p>{routine.description}</p>}
406 <div className="routine-meta">
407 <span>{routine.routineItems.length} exercises</span>
408 </div>
409 </div>
410 ))}
411 </div>
412 )}
413 </div>
414 ) : !workoutStarted ? (
415 // Workout ready view
416 <div className="workout-ready card">
417 <h2>{selectedRoutine.name}</h2>
418 {selectedRoutine.description && <p className="routine-description">{selectedRoutine.description}</p>}
419
420 <div className="workout-details">
421 <div className="detail-item">
422 <span className="detail-label">Exercises:</span>
423 <span className="detail-value">{workoutExercises.length}</span>
424 </div>
425 <div className="detail-item">
426 <span className="detail-label">Sets:</span>
427 <span className="detail-value">
428 {workoutExercises.reduce((total, ex) => total + ex.sets.length, 0)}
429 </span>
430 </div>
431 </div>
432
433 <div className="exercise-preview">
434 <h3>Exercises</h3>
435 <ul className="exercise-list">
436 {workoutExercises.map((exercise, index) => (
437 <li key={`${exercise.exerciseId}-${index}`}>
438 <div className="exercise-name">{exercise.exercise.name}</div>
439 <div className="exercise-sets">{exercise.sets.length} sets</div>
440 </li>
441 ))}
442 </ul>
443 </div>
444
445 <div className="action-buttons">
446 <button
447 className="btn btn-primary btn-lg btn-block"
448 onClick={startWorkout}
449 >
450 <FaPlay /> Start Workout
451 </button>
452 <button
453 className="btn btn-secondary btn-block mt-md"
454 onClick={() => setSelectedRoutine(null)}
455 >
456 Select Different Routine
457 </button>
458 </div>
459 </div>
460 ) : workoutCompleted ? (
461 // Workout complete view
462 <div className="workout-complete card">
463 <div className="workout-summary">
464 <h2>Workout Complete!</h2>
465
466 <div className="summary-stats">
467 <div className="stat">
468 <span className="stat-label">Duration</span>
469 <span className="stat-value">{formatTime(elapsedSeconds)}</span>
470 </div>
471
472 <div className="stat">
473 <span className="stat-label">Completed</span>
474 <span className="stat-value">{calculateProgress()}%</span>
475 </div>
476 </div>
477
478 <div className="feeling-rating">
479 <p>How was your workout?</p>
480 <div className="stars">
481 {[1, 2, 3, 4, 5].map(rating => (
482 <button
483 key={rating}
484 onClick={() => setFeelingRating(rating)}
485 className="star-btn"
486 >
487 {rating <= feelingRating ? <FaStar /> : <FaRegStar />}
488 </button>
489 ))}
490 </div>
491 </div>
492
493 <div className="workout-notes form-group">
494 <label htmlFor="workout-notes">Workout Notes</label>
495 <textarea
496 id="workout-notes"
497 value={workoutNotes}
498 onChange={e => setWorkoutNotes(e.target.value)}
499 placeholder="Add notes about the overall workout..."
500 rows={3}
501 />
502 </div>
503
504 <div className="action-buttons">
505 <button
506 onClick={saveWorkout}
507 className="btn btn-primary btn-lg btn-block"
508 >
509 <FaSave /> Save Workout
510 </button>
511 </div>
512 </div>
513 </div>
514 ) : (
515 // Active workout view
516 <div className="active-workout">
517 <div className="workout-header card">
518 <h2>{selectedRoutine.name}</h2>
519
520 <div className="workout-timer">
521 <div className="timer-value">{formatTime(elapsedSeconds)}</div>
522 <div className="progress-bar">
523 <div
524 className="progress"
525 style={{ width: `${calculateProgress()}%` }}
526 ></div>
527 </div>
528 </div>
529 </div>
530
531 <div className="current-exercise card">
532 {currentExerciseIndex < workoutExercises.length ? (
533 <>
534 <h3 className="exercise-name">
535 {workoutExercises[currentExerciseIndex].exercise.name}
536 </h3>
537
538 <div className="exercise-sets">
539 <table className="sets-table">
540 <thead>
541 <tr>
542 <th>Set</th>
543 <th>Weight</th>
544 <th>Reps</th>
545 {workoutExercises[currentExerciseIndex].sets.some(s => s.originalSet.duration > 0) && (
546 <th>Time</th>
547 )}
548 <th>Done</th>
549 </tr>
550 </thead>
551 <tbody>
552 {workoutExercises[currentExerciseIndex].sets.map((set, setIndex) => (
553 <tr key={setIndex} className={set.completed ? 'completed' : ''}>
554 <td>{setIndex + 1}</td>
555 <td>
556 <input
557 type="number"
558 min="0"
559 step="1"
560 value={set.actualWeight}
561 onChange={e => handleSetDataChange(
562 currentExerciseIndex,
563 setIndex,
564 'actualWeight',
565 parseFloat(e.target.value) || 0
566 )}
567 />
568 </td>
569 <td>
570 <input
571 type="number"
572 min="0"
573 step="1"
574 value={set.actualReps}
575 onChange={e => handleSetDataChange(
576 currentExerciseIndex,
577 setIndex,
578 'actualReps',
579 parseInt(e.target.value) || 0
580 )}
581 />
582 </td>
583 {workoutExercises[currentExerciseIndex].sets.some(s => s.originalSet.duration > 0) && (
584 <td>
585 <input
586 type="number"
587 min="0"
588 step="1"
589 value={set.actualDuration}
590 onChange={e => handleSetDataChange(
591 currentExerciseIndex,
592 setIndex,
593 'actualDuration',
594 parseInt(e.target.value) || 0
595 )}
596 />
597 </td>
598 )}
599 <td>
600 <button
601 className={`btn-check ${set.completed ? 'completed' : ''}`}
602 onClick={() => toggleSetCompleted(currentExerciseIndex, setIndex)}
603 >
604 {set.completed && <FaCheck />}
605 </button>
606 </td>
607 </tr>
608 ))}
609 </tbody>
610 </table>
611 </div>
612
613 <div className="exercise-notes form-group">
614 <label htmlFor="exercise-notes">Exercise Notes</label>
615 <textarea
616 id="exercise-notes"
617 value={workoutExercises[currentExerciseIndex].notes}
618 onChange={e => handleExerciseNotes(currentExerciseIndex, e.target.value)}
619 placeholder="Add notes for this exercise..."
620 rows={2}
621 />
622 </div>
623
624 <div className="exercise-navigation">
625 {currentExerciseIndex < workoutExercises.length - 1 && (
626 <button
627 onClick={nextExercise}
628 className={`btn btn-primary ${isCurrentExerciseComplete() ? 'pulse' : ''}`}
629 disabled={!isCurrentExerciseComplete() && workoutExercises[currentExerciseIndex].sets.length > 0}
630 >
631 <FaForward /> Next Exercise
632 </button>
633 )}
634
635 {currentExerciseIndex === workoutExercises.length - 1 && isCurrentExerciseComplete() && (
636 <button
637 onClick={completeWorkout}
638 className="btn btn-primary pulse"
639 >
640 <FaStop /> Finish Workout
641 </button>
642 )}
643 </div>
644 </>
645 ) : (
646 <div>
647 <p>All exercises completed!</p>
648 <button
649 onClick={completeWorkout}
650 className="btn btn-primary"
651 >
652 <FaStop /> Finish Workout
653 </button>
654 </div>
655 )}
656 </div>
657
658 <div className="workout-nav">
659 <div className="exercises-list card">
660 <h3>Progress</h3>
661 <ul>
662 {workoutExercises.map((ex, index) => (
663 <li
664 key={`${ex.exerciseId}-${index}`}
665 className={`
666 ${index === currentExerciseIndex ? 'active' : ''}
667 ${ex.sets.every(s => s.completed) ? 'completed' : ''}
668 `}
669 onClick={() => setCurrentExerciseIndex(index)}
670 >
671 <span className="exercise-number">{index + 1}</span>
672 <span className="exercise-list-name">{ex.exercise.name}</span>
673 <span className="exercise-progress">
674 {ex.sets.filter(s => s.completed).length}/{ex.sets.length}
675 </span>
676 </li>
677 ))}
678 </ul>
679 </div>
680
681 <div className="workout-actions card">
682 <button
683 onClick={completeWorkout}
684 className="btn btn-danger btn-block"
685 >
686 <FaStop /> End Workout
687 </button>
688 </div>
689 </div>
690 </div>
691 )}
692
693 <style>{`
694 .page-header {
695 display: flex;
696 align-items: center;
697 margin-bottom: var(--spacing-lg);
698 }
699
700 .back-button {
701 margin-right: var(--spacing-md);
702 padding: var(--spacing-sm) var(--spacing-md);
703 }
704
705 /* Routine Selection */
706 .routines-list {
707 display: grid;
708 gap: var(--spacing-md);
709 }
710
711 .routine-item {
712 padding: var(--spacing-md);
713 border: 1px solid var(--border-color);
714 border-radius: var(--border-radius);
715 cursor: pointer;
716 transition: all 0.2s;
717 }
718
719 .routine-item:hover {
720 background-color: rgba(0, 122, 255, 0.05);
721 border-color: var(--primary-color);
722 }
723
724 .routine-item h3 {
725 margin: 0;
726 margin-bottom: var(--spacing-xs);
727 }
728
729 .routine-item p {
730 margin: 0;
731 margin-bottom: var(--spacing-sm);
732 color: var(--text-muted);
733 }
734
735 .routine-meta {
736 font-size: 0.9rem;
737 color: var(--text-muted);
738 }
739
740 /* Workout Ready */
741 .routine-description {
742 color: var(--text-muted);
743 margin-bottom: var(--spacing-lg);
744 }
745
746 .workout-details {
747 display: flex;
748 gap: var(--spacing-lg);
749 margin-bottom: var(--spacing-lg);
750 }
751
752 .detail-item {
753 display: flex;
754 flex-direction: column;
755 align-items: center;
756 background-color: rgba(0, 122, 255, 0.1);
757 padding: var(--spacing-md) var(--spacing-lg);
758 border-radius: var(--border-radius);
759 }
760
761 .detail-label {
762 color: var(--text-muted);
763 font-size: 0.9rem;
764 }
765
766 .detail-value {
767 font-size: 1.2rem;
768 font-weight: bold;
769 }
770
771 .exercise-preview {
772 margin-bottom: var(--spacing-lg);
773 }
774
775 .exercise-list {
776 list-style: none;
777 padding: 0;
778 margin: 0;
779 }
780
781 .exercise-list li {
782 padding: var(--spacing-sm) 0;
783 border-bottom: 1px solid var(--border-color);
784 display: flex;
785 justify-content: space-between;
786 }
787
788 .exercise-list li:last-child {
789 border-bottom: none;
790 }
791
792 .exercise-sets {
793 color: var(--text-muted);
794 font-size: 0.9rem;
795 }
796
797 .btn-lg {
798 padding: var(--spacing-md);
799 font-size: 1.1rem;
800 }
801
802 /* Active Workout */
803 .workout-header {
804 margin-bottom: var(--spacing-md);
805 padding: var(--spacing-md);
806 }
807
808 .workout-header h2 {
809 margin-bottom: var(--spacing-sm);
810 }
811
812 .workout-timer {
813 text-align: center;
814 }
815
816 .timer-value {
817 font-size: 1.5rem;
818 font-weight: bold;
819 margin-bottom: var(--spacing-xs);
820 }
821
822 .progress-bar {
823 height: 8px;
824 background-color: var(--light-gray);
825 border-radius: 4px;
826 overflow: hidden;
827 }
828
829 .progress {
830 height: 100%;
831 background-color: var(--primary-color);
832 transition: width 0.3s ease;
833 }
834
835 .current-exercise {
836 margin-bottom: var(--spacing-md);
837 padding: var(--spacing-md);
838 }
839
840 .exercise-name {
841 margin-bottom: var(--spacing-md);
842 font-size: 1.2rem;
843 }
844
845 /* Sets table */
846 .sets-table {
847 width: 100%;
848 border-collapse: collapse;
849 margin-bottom: var(--spacing-md);
850 }
851
852 .sets-table th {
853 padding: var(--spacing-sm);
854 text-align: center;
855 border-bottom: 1px solid var(--border-color);
856 font-weight: 600;
857 }
858
859 .sets-table td {
860 padding: var(--spacing-sm);
861 text-align: center;
862 border-bottom: 1px solid var(--border-color);
863 }
864
865 .sets-table tr.completed {
866 background-color: rgba(52, 199, 89, 0.1);
867 }
868
869 .sets-table input {
870 width: 60px;
871 padding: 4px;
872 text-align: center;
873 }
874
875 .btn-check {
876 width: 30px;
877 height: 30px;
878 border-radius: 50%;
879 border: 1px solid var(--border-color);
880 background: white;
881 display: flex;
882 align-items: center;
883 justify-content: center;
884 cursor: pointer;
885 }
886
887 .btn-check.completed {
888 background-color: var(--success-color);
889 color: white;
890 border-color: var(--success-color);
891 }
892
893 .exercise-notes {
894 margin-bottom: var(--spacing-md);
895 }
896
897 .exercise-navigation {
898 display: flex;
899 justify-content: flex-end;
900 }
901
902 /* Pulse animation for the next button */
903 .pulse {
904 animation: pulse 1.5s infinite;
905 }
906
907 @keyframes pulse {
908 0% {
909 box-shadow: 0 0 0 0 rgba(0, 122, 255, 0.4);
910 }
911 70% {
912 box-shadow: 0 0 0 10px rgba(0, 122, 255, 0);
913 }
914 100% {
915 box-shadow: 0 0 0 0 rgba(0, 122, 255, 0);
916 }
917 }
918
919 /* Progress list */
920 .workout-nav {
921 margin-bottom: var(--spacing-lg);
922 }
923
924 .exercises-list ul {
925 list-style: none;
926 padding: 0;
927 margin: 0;
928 }
929
930 .exercises-list li {
931 display: flex;
932 align-items: center;
933 padding: var(--spacing-sm);
934 border-bottom: 1px solid var(--border-color);
935 cursor: pointer;
936 }
937
938 .exercises-list li.active {
939 background-color: rgba(0, 122, 255, 0.1);
940 }
941
942 .exercises-list li.completed {
943 color: var(--text-muted);
944 }
945
946 .exercise-number {
947 width: 24px;
948 height: 24px;
949 border-radius: 50%;
950 background-color: var(--light-gray);
951 display: flex;
952 align-items: center;
953 justify-content: center;
954 margin-right: var(--spacing-sm);
955 font-size: 0.8rem;
956 }
957
958 .exercises-list li.completed .exercise-number {
959 background-color: var(--success-color);
960 color: white;
961 }
962
963 .exercise-list-name {
964 flex: 1;
965 }
966
967 .exercise-progress {
968 font-size: 0.8rem;
969 color: var(--text-muted);
970 }
971
972 .workout-actions {
973 margin-top: var(--spacing-md);
974 padding: var(--spacing-md);
975 }
976
977 /* Workout Complete */
978 .workout-complete {
979 text-align: center;
980 padding: var(--spacing-lg);
981 }
982
983 .workout-summary h2 {
984 margin-bottom: var(--spacing-lg);
985 }
986
987 .summary-stats {
988 display: flex;
989 justify-content: center;
990 gap: var(--spacing-xl);
991 margin-bottom: var(--spacing-lg);
992 }
993
994 .stat {
995 display: flex;
996 flex-direction: column;
997 }
998
999 .stat-label {
1000 font-size: 0.9rem;
1001 color: var(--text-muted);
1002 }
1003
1004 .stat-value {
1005 font-size: 1.5rem;
1006 font-weight: bold;
1007 }
1008
1009 .feeling-rating {
1010 margin-bottom: var(--spacing-lg);
1011 }
1012
1013 .stars {
1014 display: flex;
1015 justify-content: center;
1016 gap: var(--spacing-sm);
1017 }
1018
1019 .star-btn {
1020 background: none;
1021 border: none;
1022 font-size: 1.5rem;
1023 color: #ffb700;
1024 cursor: pointer;
1025 }
1026
1027 /* Shared */
1028 .loading {
1029 text-align: center;
1030 padding: var(--spacing-lg);
1031 color: var(--text-muted);
1032 }
1033
1034 .empty-state {
1035 text-align: center;
1036 padding: var(--spacing-lg);
1037 color: var(--text-muted);
1038 }
1039
1040 .error-message {
1041 background-color: rgba(255, 59, 48, 0.1);
1042 color: var(--danger-color);
1043 padding: var(--spacing-md);
1044 border-radius: var(--border-radius);
1045 margin-bottom: var(--spacing-lg);
1046 }
1047
1048 .success-message {
1049 background-color: rgba(52, 199, 89, 0.1);
1050 color: var(--success-color);
1051 padding: var(--spacing-md);
1052 border-radius: var(--border-radius);
1053 margin-bottom: var(--spacing-lg);
1054 }
1055
1056 .mt-md {
1057 margin-top: var(--spacing-md);
1058 }
1059
1060 .card {
1061 background-color: white;
1062 border-radius: var(--border-radius);
1063 box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
1064 padding: var(--spacing-lg);
1065 }
1066
1067 @media (min-width: 768px) {
1068 .active-workout {
1069 display: grid;
1070 grid-template-columns: 2fr 1fr;
1071 gap: var(--spacing-md);
1072 }
1073
1074 .workout-header {
1075 grid-column: 1 / -1;
1076 }
1077
1078 .workout-nav {
1079 grid-column: 2;
1080 grid-row: 2;
1081 }
1082 }
1083 `}</style>
1084 </div>
1085 );
1086};
1087
1088export default NewWorkoutPage;