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:
+49
-5
@@ -2,9 +2,12 @@ import { useState, useEffect } from 'react';
|
||||
import { StatsPanel } from './components/StatsPanel';
|
||||
import { PeriodSelector } from './components/PeriodSelector';
|
||||
import { HeatMap } from './components/HeatMap';
|
||||
import { AnimationPlayer } from './components/AnimationPlayer';
|
||||
import { fetchData } from './services/DataService';
|
||||
import type { PeriodStats } from './services/DataService';
|
||||
import type { Transaction } from './data/mockData';
|
||||
import { computeStats } from './data/mockData';
|
||||
import { useAnimation } from './hooks/useAnimation';
|
||||
|
||||
export default function App() {
|
||||
const [periodDays, setPeriodDays] = useState(7);
|
||||
@@ -15,6 +18,13 @@ export default function App() {
|
||||
const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
|
||||
const [source, setSource] = useState<'live' | 'mock'>('mock');
|
||||
|
||||
const animation = useAnimation(transactions, periodDays);
|
||||
|
||||
const handlePeriodChange = (days: number) => {
|
||||
animation.deactivate();
|
||||
setPeriodDays(days);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
@@ -42,22 +52,41 @@ export default function App() {
|
||||
return () => { cancelled = true; clearInterval(interval); };
|
||||
}, [periodDays]);
|
||||
|
||||
// Stats calculées sur la fenêtre courante en mode animation
|
||||
const visibleStats: PeriodStats | null = animation.active
|
||||
? {
|
||||
...computeStats(animation.visibleTransactions),
|
||||
geoCount: animation.visibleTransactions.length,
|
||||
}
|
||||
: stats;
|
||||
|
||||
return (
|
||||
<div className="flex h-svh w-full overflow-hidden bg-[#0a0b0f] text-white">
|
||||
{/* Side panel */}
|
||||
<StatsPanel stats={stats} loading={loading} periodDays={periodDays} source={source} />
|
||||
<StatsPanel
|
||||
stats={visibleStats}
|
||||
loading={loading}
|
||||
periodDays={periodDays}
|
||||
source={source}
|
||||
animationLabel={animation.active ? (animation.currentFrame?.label ?? undefined) : undefined}
|
||||
/>
|
||||
|
||||
{/* Map area */}
|
||||
<div className="relative flex-1 min-w-0">
|
||||
<HeatMap transactions={transactions} />
|
||||
<HeatMap transactions={animation.visibleTransactions} />
|
||||
|
||||
{/* Period selector — floating over map */}
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-[1000]">
|
||||
<PeriodSelector value={periodDays} onChange={setPeriodDays} />
|
||||
<PeriodSelector
|
||||
value={periodDays}
|
||||
onChange={handlePeriodChange}
|
||||
animationActive={animation.active}
|
||||
onAnimate={() => animation.active ? animation.deactivate() : animation.activate()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Transaction count + source badge */}
|
||||
{!loading && (
|
||||
{/* Transaction count + source badge (masqués en mode animation) */}
|
||||
{!loading && !animation.active && (
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 z-[1000] flex items-center gap-2">
|
||||
<div className="bg-[#0a0b0f]/80 backdrop-blur-sm border border-[#2e2f3a] rounded-full px-4 py-1.5 text-xs text-[#6b7280]">
|
||||
<span className="text-[#d4a843] font-medium">{transactions.length}</span> transactions affichées
|
||||
@@ -74,6 +103,21 @@ export default function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Animation player */}
|
||||
{animation.active && (
|
||||
<AnimationPlayer
|
||||
frames={animation.frames}
|
||||
currentIndex={animation.currentIndex}
|
||||
playing={animation.playing}
|
||||
speed={animation.speed}
|
||||
onSeek={animation.seek}
|
||||
onPlay={animation.play}
|
||||
onPause={animation.pause}
|
||||
onSpeedChange={animation.setSpeed}
|
||||
onClose={animation.deactivate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Loading overlay */}
|
||||
{loading && (
|
||||
<div className="absolute inset-0 z-[999] flex items-center justify-center bg-[#0a0b0f]/60 backdrop-blur-sm">
|
||||
|
||||
Reference in New Issue
Block a user