瀏覽代碼

feat(timer): add Timer component to manage and display elapsed time during workouts
feat(workout-session-header): integrate Timer component for improved time tracking and reset functionality in workout sessions

Mathias 1 月之前
父節點
當前提交
e8f88402a5
共有 2 個文件被更改,包括 60 次插入13 次删除
  1. 47 0
      src/components/ui/timer.tsx
  2. 13 13
      src/features/workout-session/ui/workout-session-header.tsx

+ 47 - 0
src/components/ui/timer.tsx

@@ -0,0 +1,47 @@
+import { useEffect, useRef, useState } from "react";
+
+export function Timer({
+  isRunning,
+  initialSeconds = 0,
+  onChange,
+}: {
+  isRunning: boolean;
+  initialSeconds?: number;
+  onChange?: (seconds: number) => void;
+}) {
+  const [seconds, setSeconds] = useState(initialSeconds);
+  const intervalRef = useRef<NodeJS.Timeout | null>(null);
+
+  useEffect(() => {
+    setSeconds(initialSeconds);
+  }, [initialSeconds]);
+
+  useEffect(() => {
+    if (isRunning) {
+      intervalRef.current = setInterval(() => {
+        setSeconds((s) => {
+          const next = s + 1;
+          onChange?.(next);
+          return next;
+        });
+      }, 1000);
+    } else if (intervalRef.current) {
+      clearInterval(intervalRef.current);
+      intervalRef.current = null;
+    }
+    return () => {
+      if (intervalRef.current) clearInterval(intervalRef.current);
+    };
+  }, [isRunning, onChange]);
+
+  // Format mm:ss ou hh:mm:ss
+  const format = () => {
+    const h = Math.floor(seconds / 3600);
+    const m = Math.floor((seconds % 3600) / 60);
+    const s = seconds % 60;
+    if (h > 0) return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+    return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`;
+  };
+
+  return <span>{format()}</span>;
+}

+ 13 - 13
src/features/workout-session/ui/workout-session-header.tsx

@@ -6,6 +6,7 @@ import { Clock, Play, Pause, RotateCcw, X, Target } from "lucide-react";
 import { useI18n } from "locales/client";
 import { cn } from "@/shared/lib/utils";
 import { useWorkoutSession } from "@/features/workout-session/model/use-workout-session";
+import { Timer } from "@/components/ui/timer";
 import { Button } from "@/components/ui/button";
 
 import { QuitWorkoutDialog } from "../../workout-builder/ui/quit-workout-dialog";
@@ -31,6 +32,7 @@ export function WorkoutSessionHeader({
 }: WorkoutSessionHeaderProps) {
   const t = useI18n();
   const [showQuitDialog, setShowQuitDialog] = useState(false);
+  const [resetCount, setResetCount] = useState(0);
 
   const { getExercisesCompleted, getTotalExercises } = useWorkoutSession();
   const exercisesCompleted = getExercisesCompleted();
@@ -50,12 +52,15 @@ export function WorkoutSessionHeader({
     setShowQuitDialog(false);
   };
 
+  const handleReset = () => {
+    onResetTimer();
+    setResetCount((c) => c + 1);
+  };
+
   return (
     <>
       <div className="w-full mb-8">
-        {/* Minimal header, fond blanc en clair, dégradé en dark */}
         <div className="rounded-xl p-3 bg-slate-50">
-          {/* Top row - Status et Quit button */}
           <div className="flex items-center justify-between mb-4">
             <div className="flex items-center gap-2">
               <div className="w-2 h-2 rounded-full bg-emerald-400 animate-pulse"></div>
@@ -74,9 +79,8 @@ export function WorkoutSessionHeader({
             </Button>
           </div>
 
-          {/* Main content - Cards */}
           <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
-            {/* Card 1: Temps écoulé */}
+            {/* Card 1: elapsed time */}
             <div className="bg-white dark:bg-gradient-to-br dark:from-slate-800/80 dark:to-slate-700/80 rounded-lg p-3 border border-slate-100 dark:border-slate-600/30">
               <div className="flex items-center gap-2 mb-2">
                 <div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center">
@@ -87,11 +91,11 @@ export function WorkoutSessionHeader({
                 </div>
               </div>
 
-              {/* Chrono display - Large et centré */}
               <div className="text-center">
-                <div className="text-2xl font-mono font-bold text-slate-900 dark:text-white mb-2 tracking-wider">{elapsedTime}</div>
+                <div className="text-2xl font-mono font-bold text-slate-900 dark:text-white mb-2 tracking-wider">
+                  <Timer initialSeconds={typeof elapsedTime === "number" ? elapsedTime : 0} isRunning={isTimerRunning} key={resetCount} />
+                </div>
 
-                {/* Timer controls */}
                 <div className="flex items-center justify-center gap-2">
                   <Button
                     className={cn(
@@ -105,7 +109,7 @@ export function WorkoutSessionHeader({
 
                   <Button
                     className="w-8 h-8 rounded-full p-0 border-slate-200 text-slate-400 hover:bg-slate-100 dark:border-slate-600 hover:dark:bg-slate-700"
-                    onClick={onResetTimer}
+                    onClick={handleReset}
                     variant="outline"
                   >
                     <RotateCcw className="h-4 w-4" />
@@ -114,7 +118,7 @@ export function WorkoutSessionHeader({
               </div>
             </div>
 
-            {/* Card 2: Progression */}
+            {/* Card 2: progress */}
             <div className="bg-white dark:bg-gradient-to-br dark:from-slate-800/80 dark:to-slate-700/80 rounded-lg p-3 border border-slate-100 dark:border-slate-600/30">
               <div className="flex items-center gap-2 mb-2">
                 <div className="w-8 h-8 rounded-full bg-purple-500/20 flex items-center justify-center">
@@ -128,13 +132,11 @@ export function WorkoutSessionHeader({
               </div>
 
               <div className="space-y-2">
-                {/* Progress display */}
                 <div className="flex items-center justify-between">
                   <span className="text-lg font-bold text-slate-900 dark:text-white">{exercisesCompleted}</span>
                   <span className="text-slate-400">/ {totalExercises}</span>
                 </div>
 
-                {/* Progress bar */}
                 <div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2 overflow-hidden">
                   <div
                     className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-500 ease-out"
@@ -142,7 +144,6 @@ export function WorkoutSessionHeader({
                   />
                 </div>
 
-                {/* Percentage */}
                 <div className="text-center">
                   <span className="text-xs text-slate-400">
                     {Math.round((exercisesCompleted / totalExercises) * 100)}% {t("workout_builder.session.complete")}
@@ -154,7 +155,6 @@ export function WorkoutSessionHeader({
         </div>
       </div>
 
-      {/* Dialog de confirmation pour quitter */}
       <QuitWorkoutDialog
         elapsedTime={elapsedTime}
         exercisesCompleted={exercisesCompleted}