feat: vue dividende universel — overlay membres actifs géolocalisés
ci/woodpecker/push/woodpecker Pipeline was successful

Bouton DU (gauche carte) : affiche en overlay des cercles verts
proportionnels au nombre de membres WoT actifs géolocalisés par ville.
Chargement à la demande, mis en cache 1h.

Pipeline :
  SubsquidAdapter.fetchActiveMemberKeys() → isMember:true (~7000)
  CesiumAdapter.resolveGeoByKeysBatched() → lots de 500 clés
  DataService.fetchMemberCities()         → agrégation + cache 1h
  HeatMap → CircleMarkers Leaflet en overlay

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
syoul
2026-03-28 12:57:19 +01:00
parent 0136ff9ce1
commit 7c9d626b98
5 changed files with 168 additions and 6 deletions
+39 -3
View File
@@ -5,8 +5,8 @@ import { HeatMap } from './components/HeatMap';
import { FlowMap } from './components/FlowMap';
import { AnimationPlayer } from './components/AnimationPlayer';
import { SearchBar } from './components/SearchBar';
import { fetchData } from './services/DataService';
import type { PeriodStats } from './services/DataService';
import { fetchData, fetchMemberCities } from './services/DataService';
import type { PeriodStats, MemberCity } from './services/DataService';
import type { Transaction } from './data/mockData';
import type { TransactionArc } from './data/arcData';
import { computeStats } from './data/mockData';
@@ -31,8 +31,26 @@ export default function App() {
const [focusCity, setFocusCity] = useState<string | null>(initialUrlState.city);
const [panelOpen, setPanelOpen] = useState(false);
const [infoOpen, setInfoOpen] = useState(false);
const [showMembers, setShowMembers] = useState(false);
const [memberCities, setMemberCities] = useState<MemberCity[]>([]);
const [membersLoading, setMembersLoading] = useState(false);
const isMobile = useMediaQuery('(max-width: 639px)');
const toggleMembers = async () => {
if (showMembers) { setShowMembers(false); return; }
if (memberCities.length > 0) { setShowMembers(true); return; }
setMembersLoading(true);
try {
const cities = await fetchMemberCities();
setMemberCities(cities);
setShowMembers(true);
} catch (err) {
console.warn('fetchMemberCities error:', err);
} finally {
setMembersLoading(false);
}
};
const animation = useAnimation(transactions, arcs, periodDays, allTimestamps);
// Synchronise l'état dans l'URL (deep link / partage)
@@ -142,7 +160,10 @@ export default function App() {
{/* Map area */}
<div className="relative flex-1 min-w-0">
{viewMode === 'heatmap' ? (
<HeatMap transactions={animation.visibleTransactions} />
<HeatMap
transactions={animation.visibleTransactions}
memberCities={showMembers ? memberCities : []}
/>
) : (
<FlowMap
arcs={animation.active ? animation.visibleArcs : arcs}
@@ -181,6 +202,21 @@ export default function App() {
/>
</div>
{/* Toggle overlay membres DU */}
<button
onClick={toggleMembers}
disabled={membersLoading}
title={showMembers ? 'Masquer les membres' : 'Afficher les membres Ğ1 actifs géolocalisés'}
className={`absolute ${isMobile ? 'top-40' : 'top-28'} left-4 z-[1001] w-10 h-10 backdrop-blur-sm border rounded-xl flex items-center justify-center text-sm transition-colors
${showMembers
? 'bg-[#00c853]/20 border-[#00c853]/60 text-[#00c853]'
: 'bg-[#0a0b0f]/90 border-[#2e2f3a] text-[#6b7280] hover:text-[#00c853]'
}`}
aria-label="Membres DU"
>
{membersLoading ? <span className="animate-spin inline-block text-xs"></span> : 'DU'}
</button>
{/* Period selector — floating over map */}
<div className={`absolute top-4 z-[1000] ${isMobile ? 'left-16 right-4' : 'left-1/2 -translate-x-1/2'}`}>
<PeriodSelector