fix: géolocalisation Cesium+ et tests déterministes

- CesiumAdapter : utilise le champ `title` (analysé ES) au lieu de `title.keyword`
  qui retournait 0 résultats ; coerce lat/lon en number (certains profils stockent des strings)
- DataService : sépare totalVolume (all tx blockchain) de geoCount (tx heatmap)
- StatsPanel : barre de couverture géo uniquement en mode live
- App : badge source "● live Ğ1v2" ou "○ mock"
- DataService.test.ts : mock SubsquidAdapter + CesiumAdapter directement (vi.mock hoistés)
  pour que les tests soient déterministes quel que soit VITE_USE_LIVE_API dans .env.local
- tsconfig.app.json : exclude src/test pour éviter les erreurs de build prod

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
syoul
2026-03-22 16:32:23 +01:00
parent 2f813c5fdf
commit 93daec7631
7 changed files with 142 additions and 60 deletions

View File

@@ -4,6 +4,7 @@ interface StatsPanelProps {
stats: PeriodStats | null;
loading: boolean;
periodDays: number;
source: 'live' | 'mock';
}
const MEDALS = ['🥇', '🥈', '🥉'];
@@ -18,8 +19,11 @@ function StatCard({ label, value, sub }: { label: string; value: string; sub?: s
);
}
export function StatsPanel({ stats, loading, periodDays }: StatsPanelProps) {
export function StatsPanel({ stats, loading, periodDays, source }: StatsPanelProps) {
const periodLabel = periodDays === 1 ? '24 dernières heures' : `${periodDays} derniers jours`;
const geoPct = stats && stats.transactionCount > 0
? Math.round((stats.geoCount / stats.transactionCount) * 100)
: null;
return (
<aside className="w-72 shrink-0 flex flex-col gap-4 bg-[#0a0b0f]/95 backdrop-blur-sm border-r border-[#1e1f2a] p-5 overflow-y-auto">
@@ -57,6 +61,22 @@ export function StatsPanel({ stats, loading, periodDays }: StatsPanelProps) {
value={stats.transactionCount.toLocaleString('fr-FR')}
sub={`${(stats.totalVolume / (stats.transactionCount || 1)).toFixed(2)} Ğ1 / tx`}
/>
{/* Couverture géo — uniquement en mode live */}
{source === 'live' && geoPct !== null && (
<div className="bg-[#0f1016] border border-[#2e2f3a] rounded-xl p-3">
<div className="flex justify-between items-center mb-1.5">
<p className="text-[#4b5563] text-xs uppercase tracking-widest">Géolocalisées</p>
<p className="text-[#6b7280] text-xs">{stats.geoCount} / {stats.transactionCount}</p>
</div>
<div className="w-full bg-[#1e1f2a] rounded-full h-1.5">
<div
className="bg-[#d4a843] h-1.5 rounded-full transition-all duration-500"
style={{ width: `${geoPct}%` }}
/>
</div>
<p className="text-[#4b5563] text-xs mt-1 text-right">{geoPct}% via Cesium+</p>
</div>
)}
</div>
) : null}
@@ -87,7 +107,7 @@ export function StatsPanel({ stats, loading, periodDays }: StatsPanelProps) {
{/* Footer */}
<div className="mt-auto pt-4 border-t border-[#1e1f2a]">
<p className="text-[#2e2f3a] text-xs text-center">
Données simulées · API Subsquid prête
{source === 'live' ? 'Ğ1v2 · Subsquid + Cesium+' : 'Données simulées · mock'}
</p>
</div>
</aside>