From a8792641abc592dd5aa80f7ffd6904126bee6bcb Mon Sep 17 00:00:00 2001 From: syoul Date: Sun, 22 Mar 2026 18:47:53 +0100 Subject: [PATCH] feat: show up/down delta indicators on stats after each refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Volume total, transaction count, and top city volumes now display ↑ (green) or ↓ (red) arrows compared to the previous refresh, making it visible that data is actually updating. Co-Authored-By: Claude Sonnet 4.6 --- src/components/StatsPanel.tsx | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/StatsPanel.tsx b/src/components/StatsPanel.tsx index b76d9e6..b7a9ea1 100644 --- a/src/components/StatsPanel.tsx +++ b/src/components/StatsPanel.tsx @@ -1,3 +1,4 @@ +import { useRef } from 'react'; import type { PeriodStats } from '../services/DataService'; interface StatsPanelProps { @@ -9,11 +10,15 @@ interface StatsPanelProps { const MEDALS = ['🥇', '🥈', '🥉']; -function StatCard({ label, value, sub }: { label: string; value: string; sub?: string }) { +function StatCard({ label, value, sub, delta }: { label: string; value: string; sub?: string; delta?: 'up' | 'down' | null }) { return (

{label}

-

{value}

+

+ {value} + {delta === 'up' && } + {delta === 'down' && } +

{sub &&

{sub}

}
); @@ -21,6 +26,26 @@ function StatCard({ label, value, sub }: { label: string; value: string; sub?: s export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelProps) { const periodLabel = periodDays === 1 ? '24 dernières heures' : `${periodDays} derniers jours`; + const prevStats = useRef(null); + + // Calcule le delta d'une valeur par rapport au refresh précédent + function delta(current: number, prevMap: Map, key: string) { + const prev = prevMap.get(key); + if (prev === undefined) return null; + if (current > prev) return ; + if (current < prev) return ; + return null; + } + + // Construit une map volume précédent par ville + const prevCityVolume = new Map( + (prevStats.current?.topCities ?? []).map((c) => [c.name, c.volume]) + ); + const prevVolume = prevStats.current?.totalVolume ?? null; + const prevTxCount = prevStats.current?.transactionCount ?? null; + + // Mémorise les stats après le rendu + if (stats && !loading) prevStats.current = stats; const geoPct = stats && stats.transactionCount > 0 ? Math.round((stats.geoCount / stats.transactionCount) * 100) : null; @@ -55,11 +80,13 @@ export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelPro prevVolume ? 'up' : stats.totalVolume < prevVolume ? 'down' : null) : null} /> prevTxCount ? 'up' : stats.transactionCount < prevTxCount ? 'down' : null) : null} /> {/* Couverture géo — uniquement en mode live */} {source === 'live' && geoPct !== null && ( @@ -96,8 +123,9 @@ export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelPro

{city.name}

{city.count} tx

- + {city.volume.toLocaleString('fr-FR', { maximumFractionDigits: 0 })} Ğ1 + {delta(city.volume, prevCityVolume, city.name)} ))}