all repos — go-lift @ 387721fc5e90ba268efbae885c9baf8e0a543f28

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  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;