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,112 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import type { Transaction } from '../data/mockData';
|
||||
|
||||
export interface TimeFrame {
|
||||
label: string;
|
||||
from: number; // Unix ms
|
||||
to: number; // Unix ms
|
||||
}
|
||||
|
||||
function buildFrames(periodDays: number): TimeFrame[] {
|
||||
const now = Date.now();
|
||||
const start = now - periodDays * 24 * 60 * 60 * 1000;
|
||||
|
||||
const fmt = (ms: number, opts: Intl.DateTimeFormatOptions) =>
|
||||
new Date(ms).toLocaleDateString('fr-FR', opts);
|
||||
|
||||
if (periodDays === 1) {
|
||||
return Array.from({ length: 24 }, (_, i) => {
|
||||
const from = start + i * 3_600_000;
|
||||
const to = from + 3_600_000;
|
||||
const h = new Date(from).getHours();
|
||||
return {
|
||||
label: `${fmt(from, { weekday: 'short', day: 'numeric', month: 'short' })} · ${h}h – ${h + 1}h`,
|
||||
from,
|
||||
to,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (periodDays === 7) {
|
||||
return Array.from({ length: 7 }, (_, i) => {
|
||||
const from = start + i * 86_400_000;
|
||||
const to = from + 86_400_000;
|
||||
return {
|
||||
label: fmt(from, { weekday: 'long', day: 'numeric', month: 'short' }),
|
||||
from,
|
||||
to,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 30 days → weekly frames
|
||||
const frames: TimeFrame[] = [];
|
||||
let cursor = start;
|
||||
let week = 1;
|
||||
while (cursor < now) {
|
||||
const from = cursor;
|
||||
const to = Math.min(cursor + 7 * 86_400_000, now);
|
||||
frames.push({
|
||||
label: `Semaine ${week} · ${fmt(from, { day: 'numeric', month: 'short' })} – ${fmt(to - 1, { day: 'numeric', month: 'short' })}`,
|
||||
from,
|
||||
to,
|
||||
});
|
||||
cursor = to;
|
||||
week++;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
export function useAnimation(transactions: Transaction[], periodDays: number) {
|
||||
const [active, setActive] = useState(false);
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
const [speed, setSpeed] = useState<1 | 2 | 4>(1);
|
||||
|
||||
const frames = useMemo(() => buildFrames(periodDays), [periodDays]);
|
||||
|
||||
// Reset cursor and playback when period or activation changes
|
||||
useEffect(() => {
|
||||
setCurrentIndex(0);
|
||||
setPlaying(false);
|
||||
}, [periodDays, active]);
|
||||
|
||||
// Auto-advance: one step every (2000 / speed) ms
|
||||
useEffect(() => {
|
||||
if (!playing || !active) return;
|
||||
const delay = 2000 / speed;
|
||||
const t = setTimeout(() => {
|
||||
setCurrentIndex((i) => {
|
||||
if (i >= frames.length - 1) {
|
||||
setPlaying(false);
|
||||
return i;
|
||||
}
|
||||
return i + 1;
|
||||
});
|
||||
}, delay);
|
||||
return () => clearTimeout(t);
|
||||
}, [playing, active, currentIndex, speed, frames.length]);
|
||||
|
||||
const visibleTransactions = useMemo(() => {
|
||||
if (!active || frames.length === 0) return transactions;
|
||||
const frame = frames[currentIndex];
|
||||
if (!frame) return transactions;
|
||||
return transactions.filter((t) => t.timestamp >= frame.from && t.timestamp < frame.to);
|
||||
}, [active, transactions, frames, currentIndex]);
|
||||
|
||||
return {
|
||||
active,
|
||||
activate: () => setActive(true),
|
||||
deactivate: () => { setActive(false); },
|
||||
playing,
|
||||
play: () => setPlaying(true),
|
||||
pause: () => setPlaying(false),
|
||||
currentIndex,
|
||||
seek: (i: number) => { setCurrentIndex(i); setPlaying(false); },
|
||||
speed,
|
||||
setSpeed,
|
||||
frames,
|
||||
currentFrame: frames[currentIndex] ?? null,
|
||||
visibleTransactions,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user