Browse Source

Merge branch 'main' of github.com:Snouzy/workout-cool

Mathias 1 month ago
parent
commit
419a82c7a3

+ 7 - 3
src/features/workout-builder/model/use-workout-stepper.ts

@@ -10,6 +10,8 @@ export function useWorkoutStepper() {
     exercisesByMuscle,
     isLoadingExercises,
     exercisesError,
+    exercisesOrder,
+    shufflingExerciseId,
     setStep,
     nextStep,
     prevStep,
@@ -18,12 +20,11 @@ export function useWorkoutStepper() {
     toggleMuscle,
     clearMuscles,
     fetchExercises,
-    exercisesOrder,
     setExercisesOrder,
     shuffleExercise,
     pickExercise,
-    isShuffling,
     deleteExercise,
+    loadFromSession,
   } = useWorkoutBuilderStore();
 
   const canProceedToStep2 = selectedEquipment.length > 0;
@@ -68,12 +69,15 @@ export function useWorkoutStepper() {
     shuffleExercise,
 
     // additional
-    isShuffling,
+    shufflingExerciseId,
 
     // pick
     pickExercise,
 
     // delete
     deleteExercise,
+
+    // load
+    loadFromSession,
   };
 }

+ 4 - 4
src/features/workout-builder/model/workout-builder.store.ts

@@ -15,7 +15,7 @@ interface WorkoutBuilderState {
   isLoadingExercises: boolean;
   exercisesError: any; //TODO: type this
   exercisesOrder: string[];
-  isShuffling: boolean;
+  shufflingExerciseId: string | null;
 
   // Actions
   setStep: (step: WorkoutBuilderStep) => void;
@@ -49,7 +49,7 @@ export const useWorkoutBuilderStore = create<WorkoutBuilderState>((set, get) =>
   isLoadingExercises: false,
   exercisesError: null,
   exercisesOrder: [],
-  isShuffling: false,
+  shufflingExerciseId: null,
 
   setStep: (step) => set({ currentStep: step }),
   nextStep: () => set((state) => ({ currentStep: Math.min(state.currentStep + 1, 3) as WorkoutBuilderStep })),
@@ -108,7 +108,7 @@ export const useWorkoutBuilderStore = create<WorkoutBuilderState>((set, get) =>
     })),
 
   shuffleExercise: async (exerciseId, muscle) => {
-    set({ isShuffling: true });
+    set({ shufflingExerciseId: exerciseId });
     try {
       const { selectedEquipment, exercisesByMuscle } = get();
 
@@ -144,7 +144,7 @@ export const useWorkoutBuilderStore = create<WorkoutBuilderState>((set, get) =>
       console.error("Error shuffling exercise:", error);
       throw error;
     } finally {
-      set({ isShuffling: false });
+      set({ shufflingExerciseId: null });
     }
   },
 

+ 65 - 65
src/features/workout-builder/ui/exercise-list-item.tsx

@@ -1,5 +1,4 @@
-/* eslint-disable max-len */
-import { useState } from "react";
+import { useState, useMemo, useCallback } from "react";
 import Image from "next/image";
 import { Play, Shuffle, Trash2, GripVertical, Loader2 } from "lucide-react";
 import { CSS } from "@dnd-kit/utilities";
@@ -23,77 +22,83 @@ interface ExerciseListItemProps {
   isShuffling?: boolean;
 }
 
+const MUSCLE_CONFIGS: Record<string, { color: string; bg: string }> = {
+  ABDOMINALS: { color: "text-red-600 dark:text-red-400", bg: "bg-red-50 dark:bg-red-950/50" },
+  BICEPS: { color: "text-purple-600 dark:text-purple-400", bg: "bg-purple-50 dark:bg-purple-950/50" },
+  BACK: { color: "text-blue-600 dark:text-blue-400", bg: "bg-blue-50 dark:bg-blue-950/50" },
+  CHEST: { color: "text-green-600 dark:text-green-400", bg: "bg-green-50 dark:bg-green-950/50" },
+  SHOULDERS: { color: "text-orange-600 dark:text-orange-400", bg: "bg-orange-50 dark:bg-orange-950/50" },
+  OBLIQUES: { color: "text-pink-600 dark:text-pink-400", bg: "bg-pink-50 dark:bg-pink-950/50" },
+};
+
+const DEFAULT_MUSCLE_CONFIG = { color: "text-slate-600 dark:text-slate-400", bg: "bg-slate-50 dark:bg-slate-950/50" };
+
 export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete, isShuffling }: ExerciseListItemProps) {
   const t = useI18n();
-  const [isHovered, setIsHovered] = useState(false);
   const locale = useCurrentLocale();
-  const exerciseName = locale === "fr" ? exercise.name : exercise.nameEn;
   const [showVideo, setShowVideo] = useState(false);
   const [showPickModal, setShowPickModal] = useState(false);
 
-  // dnd-kit sortable
+  const exerciseName = useMemo(() => (locale === "fr" ? exercise.name : exercise.nameEn), [locale, exercise.name, exercise.nameEn]);
+  const muscleConfig = useMemo(() => MUSCLE_CONFIGS[muscle] || DEFAULT_MUSCLE_CONFIG, [muscle]);
+
   const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: exercise.id });
 
-  const style = {
-    transform: CSS.Transform.toString(transform),
-    transition,
-    zIndex: isDragging ? 50 : undefined,
-    boxShadow: isDragging ? "0 4px 16px 0 rgba(0,0,0,0.10)" : undefined,
-  };
+  const style = useMemo(
+    () => ({
+      transform: CSS.Transform.toString(transform),
+      transition,
+      zIndex: isDragging ? 50 : 1,
+      boxShadow: isDragging ? "0 4px 16px 0 rgba(0,0,0,0.10)" : undefined,
+    }),
+    [transform, transition, isDragging],
+  );
 
-  const handleOpenVideo = () => {
+  // Mémoriser les handlers
+  const handleOpenVideo = useCallback(() => {
     setShowVideo(true);
-  };
-
-  const _handleOpenPickModal = () => {
-    setShowPickModal(true);
-  };
+  }, []);
 
-  const handleClosePickModal = () => {
+  const handleClosePickModal = useCallback(() => {
     setShowPickModal(false);
-  };
+  }, []);
 
-  const handleConfirmPick = () => {
+  const handleConfirmPick = useCallback(() => {
     onPick(exercise.id);
-  };
-
-  // Déterminer la couleur du muscle
-  const getMuscleConfig = (muscle: string) => {
-    const configs: Record<string, { color: string; bg: string }> = {
-      ABDOMINALS: { color: "text-red-600 dark:text-red-400", bg: "bg-red-50 dark:bg-red-950/50" },
-      BICEPS: { color: "text-purple-600 dark:text-purple-400", bg: "bg-purple-50 dark:bg-purple-950/50" },
-      BACK: { color: "text-blue-600 dark:text-blue-400", bg: "bg-blue-50 dark:bg-blue-950/50" },
-      CHEST: { color: "text-green-600 dark:text-green-400", bg: "bg-green-50 dark:bg-green-950/50" },
-      SHOULDERS: { color: "text-orange-600 dark:text-orange-400", bg: "bg-orange-50 dark:bg-orange-950/50" },
-      OBLIQUES: { color: "text-pink-600 dark:text-pink-400", bg: "bg-pink-50 dark:bg-pink-950/50" },
-    };
-    return configs[muscle] || { color: "text-slate-600 dark:text-slate-400", bg: "bg-slate-50 dark:bg-slate-950/50" };
-  };
-
-  const muscleConfig = getMuscleConfig(muscle);
+  }, [onPick, exercise.id]);
+
+  const handleShuffle = useCallback(() => {
+    onShuffle(exercise.id, muscle);
+  }, [onShuffle, exercise.id, muscle]);
+
+  const handleDelete = useCallback(() => {
+    onDelete(exercise.id, muscle);
+  }, [onDelete, exercise.id, muscle]);
+
+  const muscleTitle = useMemo(() => t(("workout_builder.muscles." + muscle.toLowerCase()) as keyof typeof t), [t, muscle]);
 
   return (
     <div
       className={`
         group relative overflow-hidden transition-all duration-300 ease-out
-        bg-white dark:bg-slate-900 hover:bg-slate-50 dark:hover:bg-slate-800/70
+        bg-white dark:bg-slate-900 sm:hover:bg-slate-50 dark:sm:hover:bg-slate-800/70
         border-b border-slate-200 dark:border-slate-700/50
-        ${isHovered ? "shadow-lg shadow-slate-200/50 dark:shadow-slate-900/50" : ""}
+        sm:hover:shadow-lg sm:hover:shadow-slate-200/50 dark:sm:hover:shadow-slate-900/50
         ${isDragging ? "ring-2 ring-blue-400" : ""}
       `}
-      onMouseEnter={() => setIsHovered(true)}
-      onMouseLeave={() => setIsHovered(false)}
       ref={setNodeRef}
       style={style}
     >
       <div className="relative flex items-center justify-between py-2 px-2">
         <div className="flex items-center gap-2 sm:gap-4 flex-1 min-w-0">
           {/* Drag handle */}
-          <GripVertical
-            className="h-5 w-5 text-slate-400 dark:text-slate-500 cursor-grab active:cursor-grabbing"
+          <div
+            className="flex items-center justify-center p-2 -m-2 touch-manipulation cursor-grab active:cursor-grabbing"
             {...attributes}
             {...listeners}
-          />
+          >
+            <GripVertical className="h-6 w-6 sm:h-5 sm:w-5 text-slate-400 dark:text-slate-500" />
+          </div>
 
           {exercise.fullVideoImageUrl && (
             <div className="relative w-10 h-10 rounded-lg overflow-hidden shrink-0 bg-slate-200 dark:bg-slate-800 cursor-pointer border border-slate-200 dark:border-slate-700/50">
@@ -101,20 +106,22 @@ export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete
                 alt={exerciseName ?? ""}
                 className="w-full h-full object-cover scale-[1.5]"
                 height={40}
+                loading="lazy"
                 onError={(e) => {
                   e.currentTarget.style.display = "none";
                 }}
+                priority={false}
                 src={exercise.fullVideoImageUrl}
                 width={40}
               />
-              <div className="absolute inset-0 bg-black/20 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-200">
+              <div className="absolute inset-0 bg-black/20 flex items-center justify-center opacity-0 sm:group-hover:opacity-100 transition-opacity duration-200">
                 <Play className="h-3 w-3 text-white fill-current" onClick={handleOpenVideo} />
               </div>
             </div>
           )}
 
           {/* Badge muscle avec animation */}
-          <InlineTooltip className="cursor-pointer" title={t(("workout_builder.muscles." + muscle.toLowerCase()) as keyof typeof t)}>
+          <InlineTooltip className="cursor-pointer" title={muscleTitle}>
             <div
               className={`
             relative flex items-center justify-center w-5 h-5 rounded-sm font-bold text-xs shrink-0
@@ -129,7 +136,7 @@ export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete
 
           {/* Nom de l'exercice avec indicateurs */}
           <InlineTooltip className="tooltip tooltip-bottom z-50 max-w-[300px]" title={exerciseName || ""}>
-            <div className="flex-1 min-w-0 ">
+            <div className="flex-1 min-w-0 items">
               <div className="flex items-center gap-3 mb-1">
                 <h3 className="font-semibold text-slate-900 dark:text-slate-200 md:truncate text-sm">{exerciseName}</h3>
               </div>
@@ -137,38 +144,31 @@ export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete
           </InlineTooltip>
         </div>
 
-        <div className="flex items-center gap-1 sm:gap-2 shrink-0">
+        <div className="flex items-center gap-1 sm:gap-2 shrink-0 ml-1">
           {/* Bouton shuffle */}
           <Button
-            className="p-1 sm:p-2"
+            className="p-2 sm:p-2 min-h-[44px] min-w-[44px] sm:min-h-min sm:min-w-min touch-manipulation"
             disabled={isShuffling}
-            onClick={() => onShuffle(exercise.id, muscle)}
+            onClick={handleShuffle}
             size="small"
             variant="outline"
           >
-            {isShuffling ? <Loader2 className="h-3.5 w-3.5 animate-spin" /> : <Shuffle className="h-3.5 w-3.5" />}
-            <span className="hidden sm:inline">{t("workout_builder.exercise.shuffle")}</span>
+            {isShuffling ? (
+              <Loader2 className="h-4 w-4 sm:h-3.5 sm:w-3.5 animate-spin" />
+            ) : (
+              <Shuffle className="h-4 w-4 sm:h-3.5 sm:w-3.5" />
+            )}
+            <span className="hidden sm:inline ml-1">{t("workout_builder.exercise.shuffle")}</span>
           </Button>
 
-          {/* Bouton pick */}
-          {/* TODO: V2 */}
-          {/* <Button
-            className="p-1 sm:p-2 bg-blue-50 dark:bg-blue-950/50 hover:bg-blue-100 dark:hover:bg-blue-950 text-blue-600 dark:text-blue-400 border-2 border-blue-200 dark:border-blue-800"
-            onClick={handleOpenPickModal}
-            size="small"
-          >
-            <Star className="h-3.5 w-3.5" />
-            <span className="hidden sm:inline">{t("workout_builder.exercise.pick")}</span>
-          </Button> */}
-
           {/* Bouton delete */}
           <Button
-            className="p-1 sm:p-2 bg-red-50 dark:bg-red-950/50 hover:bg-red-100 dark:hover:bg-red-950 text-red-600 dark:text-red-400 border-0 rounded-lg  group-hover:opacity-100 transition-all duration-200 hover:scale-110"
-            onClick={() => onDelete(exercise.id, muscle)}
+            className="p-2 sm:p-2 min-h-[44px] min-w-[44px] sm:min-h-min sm:min-w-min bg-red-50 dark:bg-red-950/50 sm:hover:bg-red-100 dark:sm:hover:bg-red-950 text-red-600 dark:text-red-400 border-0 rounded-lg opacity-100 sm:group-hover:opacity-100 transition-all duration-200 sm:hover:scale-110 touch-manipulation"
+            onClick={handleDelete}
             size="small"
             variant="ghost"
           >
-            <Trash2 className="h-3.5 w-3.5" />
+            <Trash2 className="h-4 w-4 sm:h-3.5 sm:w-3.5" />
           </Button>
         </div>
       </div>

+ 0 - 1
src/features/workout-builder/ui/exercise-video-modal.tsx

@@ -12,7 +12,6 @@ interface ExerciseVideoModalProps {
 }
 
 export function ExerciseVideoModal({ open, onOpenChange, exercise }: ExerciseVideoModalProps) {
-  console.log("exercise:", exercise);
   const t = useI18n();
   const locale = useCurrentLocale();
   const title = locale === "fr" ? exercise.name : exercise.nameEn || exercise.name;

+ 51 - 42
src/features/workout-builder/ui/exercises-selection.tsx

@@ -1,8 +1,8 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useCallback, useMemo } from "react";
 import { Loader2, Plus } from "lucide-react";
 import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
 import { restrictToVerticalAxis } from "@dnd-kit/modifiers";
-import { DndContext, closestCenter, PointerSensor, useSensor, useSensors, DragEndEvent } from "@dnd-kit/core";
+import { DndContext, closestCenter, TouchSensor, MouseSensor, useSensor, useSensors, DragEndEvent } from "@dnd-kit/core";
 
 import { useI18n } from "locales/client";
 
@@ -19,7 +19,7 @@ interface ExercisesSelectionProps {
   onPick: (exerciseId: string) => void;
   onDelete: (exerciseId: string, muscle: string) => void;
   onAdd: () => void;
-  isShuffling?: boolean;
+  shufflingExerciseId?: string | null;
 }
 
 export const ExercisesSelection = ({
@@ -30,57 +30,66 @@ export const ExercisesSelection = ({
   onPick,
   onDelete,
   onAdd,
-  isShuffling,
+  shufflingExerciseId,
 }: ExercisesSelectionProps) => {
   const t = useI18n();
   const [flatExercises, setFlatExercises] = useState<{ id: string; muscle: string; exercise: ExerciseWithAttributes }[]>([]);
   const { setExercisesOrder, exercisesOrder } = useWorkoutStepper();
   const sensors = useSensors(
-    useSensor(PointerSensor, {
+    useSensor(MouseSensor, {
       activationConstraint: {
         distance: 5,
       },
     }),
+    useSensor(TouchSensor, {
+      activationConstraint: {
+        delay: 100,
+        tolerance: 5,
+      },
+    }),
   );
 
-  useEffect(() => {
-    if (exercisesByMuscle.length > 0) {
-      const flat = exercisesByMuscle.flatMap((group) =>
-        group.exercises.map((exercise) => ({
-          id: exercise.id,
-          muscle: group.muscle,
-          exercise,
-        })),
-      );
+  const sortableItems = useMemo(() => flatExercises.map((item) => item.id), [flatExercises]);
 
-      // if exerciseOrder is not empty, we need to order the exercises
-      if (exercisesOrder.length > 0) {
-        const orderedFlat = exercisesOrder.map((id) => flat.find((item) => item.id === id)).filter(Boolean) as typeof flat;
+  const flatExercisesComputed = useMemo(() => {
+    if (exercisesByMuscle.length === 0) return [];
 
-        // add new exercises that are not in exercisesOrder
-        const newExercises = flat.filter((item) => !exercisesOrder.includes(item.id));
+    const flat = exercisesByMuscle.flatMap((group) =>
+      group.exercises.map((exercise) => ({
+        id: exercise.id,
+        muscle: group.muscle,
+        exercise,
+      })),
+    );
 
-        setFlatExercises([...orderedFlat, ...newExercises]);
-      } else {
-        setFlatExercises(flat);
-      }
-    } else {
-      setFlatExercises([]);
-    }
+    if (exercisesOrder.length === 0) return flat;
+
+    const exerciseMap = new Map(flat.map((item) => [item.id, item]));
+    const orderedFlat = exercisesOrder.map((id) => exerciseMap.get(id)).filter(Boolean) as typeof flat;
+    const newExercises = flat.filter((item) => !exercisesOrder.includes(item.id));
+
+    return [...orderedFlat, ...newExercises];
   }, [exercisesByMuscle, exercisesOrder]);
 
-  const handleDragEnd = (event: DragEndEvent) => {
-    const { active, over } = event;
-    if (active.id !== over?.id) {
-      setFlatExercises((items) => {
-        const oldIndex = items.findIndex((item) => item.id === active.id);
-        const newIndex = items.findIndex((item) => item.id === over?.id);
-        const newOrder = arrayMove(items, oldIndex, newIndex);
-        setExercisesOrder(newOrder.map((item) => item.id));
-        return newOrder;
-      });
-    }
-  };
+  useEffect(() => {
+    setFlatExercises(flatExercisesComputed);
+  }, [flatExercisesComputed]);
+
+  const handleDragEnd = useCallback(
+    (event: DragEndEvent) => {
+      const { active, over } = event;
+      if (active.id !== over?.id) {
+        setFlatExercises((items) => {
+          const oldIndex = items.findIndex((item) => item.id === active.id);
+          const newIndex = items.findIndex((item) => item.id === over?.id);
+          const newOrder = arrayMove(items, oldIndex, newIndex);
+          setExercisesOrder(newOrder.map((item) => item.id));
+          return newOrder;
+        });
+      }
+    },
+    [setExercisesOrder],
+  );
 
   if (isLoading) {
     return (
@@ -99,13 +108,13 @@ export const ExercisesSelection = ({
         <div className="max-w-4xl mx-auto">
           {/* Liste des exercices drag and drop */}
           <DndContext collisionDetection={closestCenter} modifiers={[restrictToVerticalAxis]} onDragEnd={handleDragEnd} sensors={sensors}>
-            <SortableContext items={flatExercises.map((item) => item.id)} strategy={verticalListSortingStrategy}>
+            <SortableContext items={sortableItems} strategy={verticalListSortingStrategy}>
               <div className="bg-white dark:bg-slate-900 rounded-lg border border-slate-200 dark:border-slate-800 overflow-hidden">
-                {flatExercises.map((item, index) => (
+                {flatExercises.map((item) => (
                   <ExerciseListItem
                     exercise={item.exercise}
-                    isShuffling={isShuffling}
-                    key={`${item.id}-${index}`}
+                    isShuffling={shufflingExerciseId === item.exercise.id}
+                    key={item.id}
                     muscle={item.muscle}
                     onDelete={onDelete}
                     onPick={onPick}

+ 2 - 2
src/features/workout-builder/ui/workout-stepper.tsx

@@ -47,7 +47,7 @@ export function WorkoutStepper() {
     exercisesOrder,
     shuffleExercise,
     pickExercise,
-    isShuffling,
+    shufflingExerciseId,
     goToStep,
     deleteExercise,
   } = useWorkoutStepper();
@@ -220,11 +220,11 @@ export function WorkoutStepper() {
             error={exercisesError}
             exercisesByMuscle={exercisesByMuscle}
             isLoading={isLoadingExercises}
-            isShuffling={isShuffling}
             onAdd={handleAddExercise}
             onDelete={handleDeleteExercise}
             onPick={handlePickExercise}
             onShuffle={handleShuffleExercise}
+            shufflingExerciseId={shufflingExerciseId}
           />
         );
       default:

+ 25 - 6
src/features/workout-session/model/workout-session.store.ts

@@ -3,7 +3,7 @@ import { create } from "zustand";
 import { workoutSessionLocal } from "@/shared/lib/workout-session/workout-session.local";
 import { WorkoutSession } from "@/shared/lib/workout-session/types/workout-session";
 import { convertWeight, type WeightUnit } from "@/shared/lib/weight-conversion";
-import { WorkoutSessionExercise, WorkoutSet } from "@/features/workout-session/types/workout-set";
+import { WorkoutSessionExercise, WorkoutSet, WorkoutSetType, WorkoutSetUnit } from "@/features/workout-session/types/workout-set";
 import { useWorkoutBuilderStore } from "@/features/workout-builder/model/workout-builder.store";
 
 import { ExerciseWithAttributes } from "../../workout-builder/types";
@@ -174,19 +174,39 @@ export const useWorkoutSessionStore = create<WorkoutSessionState>((set, get) =>
   addSet: () => {
     const { session, currentExerciseIndex } = get();
     if (!session) return;
+
     const exIdx = currentExerciseIndex;
-    const sets = session.exercises[exIdx].sets;
+    const currentExercise = session.exercises[exIdx];
+    const sets = currentExercise.sets;
+
+    let typesToCopy: WorkoutSetType[] = ["REPS"];
+    let unitsToCopy: WorkoutSetUnit[] = [];
+
+    if (sets.length > 0) {
+      const lastSet = sets[sets.length - 1];
+
+      if (lastSet.types && lastSet.types.length > 0) {
+        typesToCopy = [...lastSet.types];
+        if (lastSet.units && lastSet.units.length > 0) {
+          unitsToCopy = [...lastSet.units];
+        }
+      }
+    }
+
     const newSet: WorkoutSet = {
-      id: `${session.exercises[exIdx].id}-set-${sets.length + 1}`,
+      id: `${currentExercise.id}-set-${sets.length + 1}`,
       setIndex: sets.length,
-      types: ["REPS"],
+      types: typesToCopy,
       valuesInt: [],
       valuesSec: [],
-      units: [],
+      units: unitsToCopy,
       completed: false,
     };
+
     const updatedExercises = session.exercises.map((ex, idx) => (idx === exIdx ? { ...ex, sets: [...ex.sets, newSet] } : ex));
+
     workoutSessionLocal.update(session.id, { exercises: updatedExercises });
+
     set({
       session: { ...session, exercises: updatedExercises },
       currentExercise: { ...updatedExercises[exIdx] },
@@ -372,7 +392,6 @@ export const useWorkoutSessionStore = create<WorkoutSessionState>((set, get) =>
 
   loadSessionFromLocal: () => {
     const currentId = workoutSessionLocal.getCurrent();
-    console.log("currentId:", currentId);
     if (currentId) {
       const session = workoutSessionLocal.getById(currentId);
       if (session && session.status === "active") {

+ 30 - 38
src/features/workout-session/ui/workout-session-header.tsx

@@ -50,15 +50,13 @@ export function WorkoutSessionHeader({ onQuitWorkout }: WorkoutSessionHeaderProp
 
   return (
     <>
-      <div className="w-full mt-4 mb-12 px-2 sm:px-6">
-        <div className="rounded-xl p-3 bg-slate-50 dark:bg-slate-900/80 border border-slate-200 dark:border-slate-700">
-          <div className="flex items-center justify-between mb-4">
-            <div className="flex items-center gap-2">
-              <span className="text-emerald-400 font-semibold text-xs uppercase tracking-wider">
-                {t("workout_builder.session.started_at")}{" "}
-                {new Date(session?.startedAt || "").toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" })}
-              </span>
-            </div>
+      <div className="w-full mt-2 mb-6 px-2 sm:px-6">
+        <div className="rounded-lg p-2 sm:p-3 bg-slate-50 dark:bg-slate-900/80 border border-slate-200 dark:border-slate-700">
+          <div className="flex items-center justify-between mb-2 sm:mb-3">
+            <span className="text-emerald-400 font-medium text-xs tracking-wide">
+              {t("workout_builder.session.started_at")}{" "}
+              {new Date(session?.startedAt || "").toLocaleTimeString(locale, { hour: "2-digit", minute: "2-digit" })}
+            </span>
 
             <Button
               className="border-red-500/30 text-red-400 hover:bg-red-500/10 hover:border-red-500 px-2 py-1 text-xs dark:border-red-700/40 dark:text-red-300 dark:hover:bg-red-700/10"
@@ -70,27 +68,25 @@ export function WorkoutSessionHeader({ onQuitWorkout }: WorkoutSessionHeaderProp
             </Button>
           </div>
 
-          <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 sm:gap-3">
-            {/* Card 2: progress */}
-            <div className="bg-white dark:bg-slate-800 rounded-lg p-2 sm:p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
-              <div className="flex items-center gap-1.5 sm:gap-2 mb-1.5 sm:mb-2">
-                <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-purple-500/20 flex items-center justify-center">
-                  <Target className="h-3 w-3 sm:h-4 sm:w-4 text-purple-400" />
-                </div>
-                <div>
-                  <h3 className="text-slate-700 dark:text-white font-semibold text-sm sm:text-base">
-                    {t("workout_builder.session.exercise_progress")}
-                  </h3>
+          <div className="grid grid-cols-2 gap-2">
+            {/* Card 1: Exercise Progress */}
+            <div className="bg-white dark:bg-slate-800 rounded-md p-2 border border-slate-200 dark:border-slate-700">
+              <div className="flex items-center gap-2 mb-2">
+                <div className="w-5 h-5 rounded-full bg-purple-500/20 flex items-center justify-center shrink-0">
+                  <Target className="h-3 w-3 text-purple-400" />
                 </div>
+                <h3 className="text-slate-700 dark:text-white font-medium text-xs truncate">
+                  {t("workout_builder.session.exercise_progress")}
+                </h3>
               </div>
 
-              <div className="space-y-1.5 sm:space-y-2">
+              <div className="space-y-1">
                 <div className="flex items-center justify-between">
-                  <span className="text-base sm:text-lg font-bold text-slate-900 dark:text-white">{exercisesCompleted}</span>
-                  <span className="text-slate-400 dark:text-slate-400">/ {totalExercises}</span>
+                  <span className="text-lg font-bold text-slate-900 dark:text-white">{exercisesCompleted}</span>
+                  <span className="text-slate-400 text-sm">/ {totalExercises}</span>
                 </div>
 
-                <div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-1.5 sm:h-2 overflow-hidden">
+                <div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-1.5 overflow-hidden">
                   <div
                     className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-500 ease-out"
                     style={{ width: `${(exercisesCompleted / totalExercises) * 100}%` }}
@@ -98,34 +94,30 @@ export function WorkoutSessionHeader({ onQuitWorkout }: WorkoutSessionHeaderProp
                 </div>
 
                 <div className="text-center">
-                  <span className="text-xs text-slate-400 dark:text-slate-400">
+                  <span className="text-xs text-slate-400">
                     {Math.round((exercisesCompleted / totalExercises) * 100)}% {t("workout_builder.session.complete")}
                   </span>
                 </div>
               </div>
             </div>
 
-            {/* Card 3: Volume Total */}
-            <div className="bg-white dark:bg-slate-800 rounded-lg p-2 sm:p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
-              <div className="flex items-center gap-1.5 sm:gap-2 mb-1.5 sm:mb-2">
-                <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-orange-500/20 flex items-center justify-center">
-                  <Weight className="h-3 w-3 sm:h-4 sm:w-4 text-orange-400" />
-                </div>
-                <div>
-                  <h3 className="text-slate-700 dark:text-white font-semibold text-sm sm:text-base">
-                    {t("workout_builder.session.total_volume")}
-                  </h3>
+            {/* Card 2: Total Volume */}
+            <div className="bg-white dark:bg-slate-800 rounded-md p-2 border border-slate-200 dark:border-slate-700">
+              <div className="flex items-center gap-2 mb-2">
+                <div className="w-5 h-5 rounded-full bg-orange-500/20 flex items-center justify-center shrink-0">
+                  <Weight className="h-3 w-3 text-orange-400" />
                 </div>
+                <h3 className="text-slate-700 dark:text-white font-medium text-xs truncate">{t("workout_builder.session.total_volume")}</h3>
               </div>
 
               <div className="text-center">
-                <div className="text-xl sm:text-2xl font-bold text-slate-900 dark:text-white mb-1">
+                <div className="text-lg font-bold text-slate-900 dark:text-white mb-1">
                   {totalVolume.toFixed(volumeUnit === "lbs" ? 1 : 0)}
                 </div>
                 <div className="flex items-center justify-center gap-1">
                   <button
                     className={cn(
-                      "text-xs px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-colors",
+                      "text-xs px-1.5 py-0.5 rounded transition-colors",
                       volumeUnit === "kg"
                         ? "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100"
                         : "text-slate-400 hover:text-slate-600 dark:hover:text-slate-300",
@@ -137,7 +129,7 @@ export function WorkoutSessionHeader({ onQuitWorkout }: WorkoutSessionHeaderProp
                   <span className="text-slate-300 dark:text-slate-600">|</span>
                   <button
                     className={cn(
-                      "text-xs px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-colors",
+                      "text-xs px-1.5 py-0.5 rounded transition-colors",
                       volumeUnit === "lbs"
                         ? "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100"
                         : "text-slate-400 hover:text-slate-600 dark:hover:text-slate-300",

+ 0 - 1
src/features/workout-session/ui/workout-session-list.tsx

@@ -68,7 +68,6 @@ export function WorkoutSessionList() {
                 .filter(Boolean),
             ),
           );
-    console.log("allMuscles:", allMuscles);
 
     const exercisesByMuscle = allMuscles.map((muscle) => ({
       muscle,