From 810c8157063283f48a9cda5cc238914077b77540 Mon Sep 17 00:00:00 2001 From: syoul Date: Tue, 24 Mar 2026 12:28:51 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20bouton=20=E2=84=B9=20+=20modale=20InfoP?= =?UTF-8?q?anel=20d=C3=A9crivant=20toutes=20les=20fonctionnalit=C3=A9s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - InfoPanel : modale avec overlay, sections Vues / Clusters / Période / Animation / Statistiques / Source, composants Section/Feature/Kbd - PeriodSelector : ajout prop onInfo + bouton ℹ en fin de barre - App.tsx : état infoOpen, onInfo → setInfoOpen(true), rendu InfoPanel Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 6 ++ src/components/InfoPanel.tsx | 142 ++++++++++++++++++++++++++++++ src/components/PeriodSelector.tsx | 13 ++- 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 src/components/InfoPanel.tsx diff --git a/src/App.tsx b/src/App.tsx index b274c01..0f9d2a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import { computeStats } from './data/mockData'; import { computeFlowStats } from './data/arcData'; import { useAnimation } from './hooks/useAnimation'; import { useMediaQuery } from './hooks/useMediaQuery'; +import { InfoPanel } from './components/InfoPanel'; export default function App() { const [periodDays, setPeriodDays] = useState(7); @@ -27,6 +28,7 @@ export default function App() { const [viewMode, setViewMode] = useState<'heatmap' | 'flow'>('heatmap'); const [focusCity, setFocusCity] = useState(null); const [panelOpen, setPanelOpen] = useState(false); + const [infoOpen, setInfoOpen] = useState(false); const isMobile = useMediaQuery('(max-width: 639px)'); const animation = useAnimation(transactions, arcs, periodDays, allTimestamps); @@ -141,6 +143,7 @@ export default function App() { geoPercent={visibleStats && visibleStats.transactionCount > 0 ? Math.round((visibleStats.geoCount / visibleStats.transactionCount) * 100) : null} + onInfo={() => setInfoOpen(true)} /> @@ -196,6 +199,9 @@ export default function App() { )} + {/* Info panel */} + {infoOpen && setInfoOpen(false)} />} + {/* Bottom drawer — mobile uniquement */} {isMobile && ( <> diff --git a/src/components/InfoPanel.tsx b/src/components/InfoPanel.tsx new file mode 100644 index 0000000..bdddedc --- /dev/null +++ b/src/components/InfoPanel.tsx @@ -0,0 +1,142 @@ +interface InfoPanelProps { + onClose: () => void; +} + +export function InfoPanel({ onClose }: InfoPanelProps) { + return ( + <> + {/* Overlay */} +
+ + {/* Modale */} +
+ + {/* En-tête */} +
+
+

ĞéoFlux

+

Explorateur de transactions Ğ1v2

+
+ +
+ + {/* Contenu */} +
+ +
+ + Densité des transactions géolocalisées. Les zones chaudes concentrent le plus d'activité. + Basculer avec le bouton Heatmap / Flux. + + + Arcs entre villes représentant les flux de Ğ1. L'épaisseur indique le volume, + la couleur la direction dominante. + +
+ +
+ + Les villes géographiquement proches sont regroupées en un nœud unique. + Le chiffre affiché indique le nombre de villes dans le groupe. + + + Chaque ville est affichée individuellement, sans regroupement. + Basculer avec le bouton ⬡ Clusters / · Villes (bas gauche de la carte). + + + Vert = receveur net (reçoit plus que ce qu'il émet) ·{' '} + Or = équilibré ·{' '} + Orange = émetteur net. + + + Affiche la liste des villes du cluster avec leur balance individuelle, + triée par valeur absolue. + +
+ +
+ + 24h 7 jours 30 jours — fenêtre glissante jusqu'à maintenant. + + + Saisir une durée de 1 à 365 jours. + +
+ +
+ + Rejoue les transactions frame par frame sur la période sélectionnée + (une frame = une journée). + + + Lecture / pause · Navigation frame par frame (◀◀ ▶▶) · + Vitesse ×1 ×2 ×4. + +
+ +
+ + Volume total en Ğ1, nombre de transactions, top émetteurs et receveurs, + répartition géographique. Se met à jour en temps réel et pendant l'animation. + + + Le panneau est accessible via le bouton en haut à gauche. + + + Pourcentage des transactions ayant une géolocalisation connue sur la période / frame courante. + +
+ +
+ + Données temps réel de la blockchain Ğ1v2, actualisées toutes les 30 secondes. + + + Données simulées, utilisées si l'API live est indisponible. + +
+ +
+
+ + ); +} + +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+
{children}
+
+ ); +} + +function Feature({ icon, name, children }: { icon: string; name: string; children: React.ReactNode }) { + return ( +
+ {icon} +
+ {name} + + {children} +
+
+ ); +} + +function Kbd({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/src/components/PeriodSelector.tsx b/src/components/PeriodSelector.tsx index c8921e9..2ccb8ad 100644 --- a/src/components/PeriodSelector.tsx +++ b/src/components/PeriodSelector.tsx @@ -8,6 +8,7 @@ interface PeriodSelectorProps { viewMode: 'heatmap' | 'flow'; onViewModeChange: (mode: 'heatmap' | 'flow') => void; geoPercent?: number | null; + onInfo: () => void; } const PERIODS = [ @@ -18,7 +19,7 @@ const PERIODS = [ const PRESET_DAYS = new Set([1, 7, 30]); -export function PeriodSelector({ value, onChange, animationActive, onAnimate, viewMode, onViewModeChange, geoPercent }: PeriodSelectorProps) { +export function PeriodSelector({ value, onChange, animationActive, onAnimate, viewMode, onViewModeChange, geoPercent, onInfo }: PeriodSelectorProps) { const [customOpen, setCustomOpen] = useState(false); const [inputVal, setInputVal] = useState(''); const inputRef = useRef(null); @@ -130,6 +131,16 @@ export function PeriodSelector({ value, onChange, animationActive, onAnimate, vi {geoPercent}% Tx géoloc. )} + +
+ +
); }