all repos — go-lift @ 092e7440440b8459d82fb90c05c005f9f38e3c51

Lightweight workout tracker prototype..

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