Files
g1flux/src/App.tsx
syoul 94474fc007 fix: détection pays fiable via nom de ville Cesium+ + reorder bounding boxes
- Extrait le pays depuis le champ city Cesium+ en priorité (ex: "Heusy, 4800, Belgique" → BE)
- Bounding boxes réordonnées : petits pays (LU, BE, CH, NL) avant FR pour éviter les faux positifs
- Affiche l'heure du dernier refresh sur le badge live

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-22 19:08:22 +01:00

90 lines
3.6 KiB
TypeScript

import { useState, useEffect } from 'react';
import { StatsPanel } from './components/StatsPanel';
import { PeriodSelector } from './components/PeriodSelector';
import { HeatMap } from './components/HeatMap';
import { fetchData } from './services/DataService';
import type { PeriodStats } from './services/DataService';
import type { Transaction } from './data/mockData';
export default function App() {
const [periodDays, setPeriodDays] = useState(7);
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [stats, setStats] = useState<PeriodStats | null>(null);
const [loading, setLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [lastUpdate, setLastUpdate] = useState<Date | null>(null);
const [source, setSource] = useState<'live' | 'mock'>('mock');
useEffect(() => {
let cancelled = false;
const load = (showLoading: boolean) => {
if (showLoading) setLoading(true);
else setRefreshing(true);
fetchData(periodDays)
.then(({ transactions, stats, source }) => {
if (!cancelled) {
setTransactions(transactions);
setStats(stats);
setSource(source);
setLastUpdate(new Date());
}
})
.catch((err) => console.warn('Ğ1Flux refresh error:', err))
.finally(() => {
if (!cancelled) { setLoading(false); setRefreshing(false); }
});
};
load(true);
const interval = setInterval(() => load(false), 30_000);
return () => { cancelled = true; clearInterval(interval); };
}, [periodDays]);
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} />
{/* Map area */}
<div className="relative flex-1 min-w-0">
<HeatMap transactions={transactions} />
{/* Period selector — floating over map */}
<div className="absolute top-4 left-1/2 -translate-x-1/2 z-[1000]">
<PeriodSelector value={periodDays} onChange={setPeriodDays} />
</div>
{/* Transaction count + source badge */}
{!loading && (
<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
</div>
<div className={`backdrop-blur-sm border rounded-full px-3 py-1.5 text-xs font-medium ${
source === 'live'
? 'bg-emerald-950/80 border-emerald-700 text-emerald-400'
: 'bg-[#0a0b0f]/80 border-[#2e2f3a] text-[#4b5563]'
}`}>
{source === 'live'
? <>{refreshing ? <span className="animate-spin inline-block"></span> : '●'} live Ğ1v2{lastUpdate && <span className="ml-1 opacity-60">{lastUpdate.toLocaleTimeString('fr-FR')}</span>}</>
: '○ mock'}
</div>
</div>
)}
{/* Loading overlay */}
{loading && (
<div className="absolute inset-0 z-[999] flex items-center justify-center bg-[#0a0b0f]/60 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3">
<div className="w-10 h-10 rounded-full border-2 border-[#d4a843] border-t-transparent animate-spin" />
<p className="text-[#d4a843] text-sm">Chargement des flux</p>
</div>
</div>
)}
</div>
</div>
);
}