'use client' /** * Germany School Map Component * * Displays a choropleth map of Germany showing school density by state. * Uses react-simple-maps for SVG rendering. */ import { useState, useEffect, useCallback, memo } from 'react' import { ComposableMap, Geographies, Geography, ZoomableGroup, } from 'react-simple-maps' // Germany GeoJSON - local file (simplified boundaries) const GERMANY_GEO_URL = '/germany-states.json' const STATE_NAMES: Record = { 'BW': 'Baden-Wuerttemberg', 'BY': 'Bayern', 'BE': 'Berlin', 'BB': 'Brandenburg', 'HB': 'Bremen', 'HH': 'Hamburg', 'HE': 'Hessen', 'MV': 'Mecklenburg-Vorpommern', 'NI': 'Niedersachsen', 'NW': 'Nordrhein-Westfalen', 'RP': 'Rheinland-Pfalz', 'SL': 'Saarland', 'SN': 'Sachsen', 'ST': 'Sachsen-Anhalt', 'SH': 'Schleswig-Holstein', 'TH': 'Thueringen', } interface SchoolStats { total_schools: number by_state?: Record } interface GermanySchoolMapProps { stats: SchoolStats | null onStateClick?: (stateCode: string) => void selectedState?: string } // Color scale for school density const getStateColor = (count: number, maxCount: number): string => { if (count === 0) return '#f1f5f9' // slate-100 const intensity = count / maxCount if (intensity < 0.2) return '#dbeafe' // blue-100 if (intensity < 0.4) return '#93c5fd' // blue-300 if (intensity < 0.6) return '#3b82f6' // blue-500 if (intensity < 0.8) return '#1d4ed8' // blue-700 return '#1e3a8a' // blue-900 } function GermanySchoolMap({ stats, onStateClick, selectedState }: GermanySchoolMapProps) { const [tooltipContent, setTooltipContent] = useState('') const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }) const [showTooltip, setShowTooltip] = useState(false) const [zoom, setZoom] = useState(1) const [center, setCenter] = useState<[number, number]>([10.5, 51.2]) const maxSchools = stats?.by_state ? Math.max(...Object.values(stats.by_state)) : 0 const handleStateHover = useCallback((geo: { properties: { code?: string; name?: string } }, evt: React.MouseEvent) => { const stateCode = geo.properties.code || '' const stateName = geo.properties.name || STATE_NAMES[stateCode] || stateCode const schoolCount = stats?.by_state?.[stateCode] || 0 setTooltipContent(`${stateName}: ${schoolCount.toLocaleString()} Schulen`) setTooltipPosition({ x: evt.clientX, y: evt.clientY }) setShowTooltip(true) }, [stats]) const handleStateLeave = useCallback(() => { setShowTooltip(false) }, []) const handleZoomIn = () => { if (zoom < 4) setZoom(zoom * 1.5) } const handleZoomOut = () => { if (zoom > 1) setZoom(zoom / 1.5) } const handleReset = () => { setZoom(1) setCenter([10.5, 51.2]) } return (
{/* Map Controls */}
{/* Legend */}

Schulen pro Bundesland

0 {maxSchools.toLocaleString()}
{/* Tooltip */} {showTooltip && (
{tooltipContent}
)} {/* Map */} { setCenter(coordinates) setZoom(newZoom) }} > {({ geographies }: { geographies: any[] }) => geographies.map((geo: any) => { const stateCode = geo.properties.code || '' const schoolCount = stats?.by_state?.[stateCode] || 0 const isSelected = selectedState === stateCode return ( handleStateHover(geo, evt)} onMouseLeave={handleStateLeave} onClick={() => onStateClick?.(stateCode)} style={{ default: { fill: isSelected ? '#f59e0b' // amber-500 for selected : getStateColor(schoolCount, maxSchools), stroke: '#ffffff', strokeWidth: 0.5, outline: 'none', }, hover: { fill: isSelected ? '#d97706' : '#60a5fa', // blue-400 stroke: '#ffffff', strokeWidth: 1, outline: 'none', cursor: 'pointer', }, pressed: { fill: '#f59e0b', stroke: '#ffffff', strokeWidth: 1, outline: 'none', }, }} /> ) }) } {/* State List */} {stats?.by_state && (

Schulen nach Bundesland

{Object.entries(stats.by_state) .sort(([,a], [,b]) => b - a) .map(([code, count]) => ( ))}
)}
) } export default memo(GermanySchoolMap)