Nouveau mode animation accessible via "▶ Animer" dans le sélecteur de période. - useAnimation : hook gérant frames, lecture, vitesse, filtrage client - AnimationPlayer : barre de contrôle (play/pause, slider, ×1/×2/×4) - Granularité auto : 24 frames/h (24h), 7 frames/jour (7j), ~4 frames/semaine (30j) - Stats et heatmap mis à jour sur la fenêtre courante, zéro requête réseau supplémentaire Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import type { TimeFrame } from '../hooks/useAnimation';
|
||||
|
||||
interface AnimationPlayerProps {
|
||||
frames: TimeFrame[];
|
||||
currentIndex: number;
|
||||
playing: boolean;
|
||||
speed: 1 | 2 | 4;
|
||||
onSeek: (i: number) => void;
|
||||
onPlay: () => void;
|
||||
onPause: () => void;
|
||||
onSpeedChange: (s: 1 | 2 | 4) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function AnimationPlayer({
|
||||
frames,
|
||||
currentIndex,
|
||||
playing,
|
||||
speed,
|
||||
onSeek,
|
||||
onPlay,
|
||||
onPause,
|
||||
onSpeedChange,
|
||||
onClose,
|
||||
}: AnimationPlayerProps) {
|
||||
const frame = frames[currentIndex];
|
||||
|
||||
return (
|
||||
<div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-[1001] w-[min(640px,90vw)]">
|
||||
<div className="bg-[#0a0b0f]/90 backdrop-blur-sm border border-[#2e2f3a] rounded-2xl px-5 py-3 flex flex-col gap-2.5 shadow-xl">
|
||||
|
||||
{/* Frame label + position */}
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-[#d4a843] text-sm font-medium">
|
||||
{frame?.label ?? '—'}
|
||||
</span>
|
||||
<span className="text-[#4b5563] text-xs tabular-nums">
|
||||
{currentIndex + 1} / {frames.length}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Slider */}
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={frames.length - 1}
|
||||
value={currentIndex}
|
||||
onChange={(e) => onSeek(Number(e.target.value))}
|
||||
className="w-full h-1 accent-[#d4a843] cursor-pointer"
|
||||
/>
|
||||
|
||||
{/* Controls row */}
|
||||
<div className="flex items-center justify-between">
|
||||
|
||||
{/* Playback buttons */}
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
onClick={() => onSeek(Math.max(0, currentIndex - 1))}
|
||||
className="px-2.5 py-1.5 text-[#6b7280] hover:text-white transition-colors text-sm"
|
||||
title="Frame précédente"
|
||||
>
|
||||
◀◀
|
||||
</button>
|
||||
<button
|
||||
onClick={playing ? onPause : onPlay}
|
||||
className="px-4 py-1.5 bg-[#d4a843] text-[#0a0b0f] rounded-lg font-bold text-sm hover:bg-[#e8c060] transition-colors min-w-[52px] text-center shadow-[0_0_10px_rgba(212,168,67,0.3)]"
|
||||
>
|
||||
{playing ? '⏸' : '▶'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onSeek(Math.min(frames.length - 1, currentIndex + 1))}
|
||||
className="px-2.5 py-1.5 text-[#6b7280] hover:text-white transition-colors text-sm"
|
||||
title="Frame suivante"
|
||||
>
|
||||
▶▶
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Speed selector */}
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-[#4b5563] text-xs mr-1">Vitesse</span>
|
||||
{([1, 2, 4] as const).map((s) => (
|
||||
<button
|
||||
key={s}
|
||||
onClick={() => onSpeedChange(s)}
|
||||
className={`px-2 py-1 rounded text-xs font-medium transition-colors ${
|
||||
speed === s
|
||||
? 'bg-[#d4a843] text-[#0a0b0f]'
|
||||
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
|
||||
}`}
|
||||
>
|
||||
×{s}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Close */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-[#4b5563] hover:text-white transition-colors px-2 py-1 text-sm ml-2"
|
||||
title="Quitter l'animation"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
interface PeriodSelectorProps {
|
||||
value: number;
|
||||
onChange: (days: number) => void;
|
||||
animationActive: boolean;
|
||||
onAnimate: () => void;
|
||||
}
|
||||
|
||||
const PERIODS = [
|
||||
@@ -9,7 +11,7 @@ const PERIODS = [
|
||||
{ label: '30 jours', days: 30 },
|
||||
];
|
||||
|
||||
export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
||||
export function PeriodSelector({ value, onChange, animationActive, onAnimate }: PeriodSelectorProps) {
|
||||
return (
|
||||
<div className="flex gap-1 bg-[#0f1016] border border-[#2e2f3a] rounded-lg p-1">
|
||||
{PERIODS.map(({ label, days }) => (
|
||||
@@ -27,6 +29,19 @@ export function PeriodSelector({ value, onChange }: PeriodSelectorProps) {
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
<div className="w-px mx-1 bg-[#2e2f3a] self-stretch" />
|
||||
<button
|
||||
onClick={onAnimate}
|
||||
className={`
|
||||
px-3 py-1.5 rounded-md text-sm font-medium transition-all duration-200 cursor-pointer
|
||||
${animationActive
|
||||
? 'bg-[#d4a843] text-[#0a0b0f] shadow-[0_0_12px_rgba(212,168,67,0.4)]'
|
||||
: 'text-[#6b7280] hover:text-[#d4a843] hover:bg-[#1a1b23]'
|
||||
}
|
||||
`}
|
||||
>
|
||||
▶ Animer
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ interface StatsPanelProps {
|
||||
loading: boolean;
|
||||
periodDays: number;
|
||||
source: 'live' | 'mock';
|
||||
animationLabel?: string;
|
||||
}
|
||||
|
||||
const MEDALS = ['🥇', '🥈', '🥉'];
|
||||
@@ -24,7 +25,7 @@ function StatCard({ label, value, sub, delta }: { label: string; value: string;
|
||||
);
|
||||
}
|
||||
|
||||
export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelProps) {
|
||||
export function StatsPanel({ stats, loading, periodDays, source, animationLabel }: StatsPanelProps) {
|
||||
const periodLabel = periodDays === 1 ? '24 dernières heures' : `${periodDays} derniers jours`;
|
||||
const prevStats = useRef<PeriodStats | null>(null);
|
||||
|
||||
@@ -70,7 +71,10 @@ export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelPro
|
||||
|
||||
{/* Period label */}
|
||||
<p className="text-[#4b5563] text-xs border-t border-[#1e1f2a] pt-3">
|
||||
Période : <span className="text-[#6b7280]">{periodLabel}</span>
|
||||
{animationLabel
|
||||
? <><span className="text-[#d4a843]">▶</span> <span className="text-[#d4a843]">{animationLabel}</span></>
|
||||
: <>Période : <span className="text-[#6b7280]">{periodLabel}</span></>
|
||||
}
|
||||
</p>
|
||||
|
||||
{/* Stats */}
|
||||
|
||||
Reference in New Issue
Block a user