feat: add GeoMap component for visualizing country-specific scan data.

This commit is contained in:
Timo 2026-01-07 18:52:07 +01:00
parent 57d6e3a449
commit 036500f6d1
1 changed files with 34 additions and 1 deletions

View File

@ -144,8 +144,16 @@ const GeoMap: React.FC<GeoMapProps> = ({ countryStats, totalScans }) => {
.domain([0, maxCount || 1]) .domain([0, maxCount || 1])
.range(['#E0F2FE', '#1E40AF']); .range(['#E0F2FE', '#1E40AF']);
const [tooltipContent, setTooltipContent] = React.useState<{ name: string; count: number } | null>(null);
const [tooltipPos, setTooltipPos] = React.useState({ x: 0, y: 0 });
return ( return (
<div className="w-full h-full"> <div
className="w-full h-full relative group"
onMouseMove={(evt) => {
setTooltipPos({ x: evt.clientX, y: evt.clientY });
}}
>
<ComposableMap <ComposableMap
projection="geoMercator" projection="geoMercator"
projectionConfig={{ projectionConfig={{
@ -179,6 +187,13 @@ const GeoMap: React.FC<GeoMapProps> = ({ countryStats, totalScans }) => {
}, },
pressed: { outline: 'none' }, pressed: { outline: 'none' },
}} }}
onMouseEnter={() => {
const { name } = geo.properties;
setTooltipContent({ name, count: scanCount });
}}
onMouseLeave={() => {
setTooltipContent(null);
}}
/> />
); );
}) })
@ -186,6 +201,24 @@ const GeoMap: React.FC<GeoMapProps> = ({ countryStats, totalScans }) => {
</Geographies> </Geographies>
</ZoomableGroup> </ZoomableGroup>
</ComposableMap> </ComposableMap>
{tooltipContent && (
<div
className="fixed z-50 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-xl pointer-events-none transform -translate-x-1/2 -translate-y-full"
style={{
left: tooltipPos.x,
top: tooltipPos.y - 10,
}}
>
<div className="flex items-center gap-2">
<span>{tooltipContent.name}</span>
<span className="text-gray-400">|</span>
<span className="font-bold text-blue-400">{tooltipContent.count} scans</span>
</div>
{/* Arrow */}
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 translate-y-full w-0 h-0 border-l-4 border-r-4 border-t-4 border-l-transparent border-r-transparent border-t-gray-900"></div>
</div>
)}
</div> </div>
); );
}; };