From a81ee8e4d09e128cf409f359fdd1478fa31ea1ec Mon Sep 17 00:00:00 2001 From: cupadev-admin Date: Sun, 8 Mar 2026 18:33:45 +0000 Subject: [PATCH] feat: add src/components/Stats.tsx --- src/components/Stats.tsx | 114 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 src/components/Stats.tsx diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx new file mode 100644 index 0000000..9cdf985 --- /dev/null +++ b/src/components/Stats.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; + +const stats = [ + { + value: 280000, + suffix: "+", + label: "Anciens militaires en France", + description: "rejoignent chaque année le civil", + }, + { + value: 94, + suffix: "%", + label: "Taux de placement", + description: "de nos candidats dans l'emploi", + }, + { + value: 3, + suffix: " mois", + label: "Durée moyenne", + description: "pour trouver un emploi avec nous", + }, + { + value: 450, + suffix: "+", + label: "Entreprises partenaires", + description: "recrutant des profils militaires", + }, +]; + +function useCountUp(target: number, duration = 2000, start = false) { + const [count, setCount] = useState(0); + + useEffect(() => { + if (!start) return; + let startTime: number | null = null; + const step = (timestamp: number) => { + if (!startTime) startTime = timestamp; + const progress = Math.min((timestamp - startTime) / duration, 1); + setCount(Math.floor(progress * target)); + if (progress < 1) requestAnimationFrame(step); + }; + requestAnimationFrame(step); + }, [target, duration, start]); + + return count; +} + +function StatCard({ + value, + suffix, + label, + description, + animate, +}: (typeof stats)[0] & { animate: boolean }) { + const count = useCountUp(value, 2000, animate); + + return ( +
+
+
+ {count.toLocaleString("fr-FR")} + {suffix} +
+
{label}
+
{description}
+
+
+ ); +} + +export default function Stats() { + const [visible, setVisible] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true); + observer.disconnect(); + } + }, + { threshold: 0.3 } + ); + if (ref.current) observer.observe(ref.current); + return () => observer.disconnect(); + }, []); + + return ( +
+
+
+

+ En chiffres +

+

+ La reconversion militaire en France +

+
+
+ {stats.map((stat) => ( + + ))} +
+
+
+ ); +}