UNPKG

analytica-frontend-lib

Version:

Repositório público dos componentes utilizados nas plataformas da Analytica Ensino

1 lines 42.6 kB
{"version":3,"sources":["../src/components/ChoroplethMap/ChoroplethMap.tsx"],"sourcesContent":["/* global google */\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { GoogleMap, useJsApiLoader } from '@react-google-maps/api';\nimport union from '@turf/union';\nimport type { Feature, MultiPolygon, Polygon } from 'geojson';\nimport { cn } from '../../utils/utils';\nimport { useTheme } from '../../hooks/useTheme';\nimport Text from '../Text/Text';\nimport Alert from '../Alert/Alert';\nimport type {\n AccessBreakdown,\n ChoroplethMapProps,\n ColorClass,\n RegionData,\n} from './ChoroplethMap.types';\n\n/**\n * Stable ID for the Google Maps script loader singleton.\n * Must NOT use useId() — the loader rejects re-initialization with different IDs.\n */\nconst GOOGLE_MAPS_LOADER_ID = 'google-maps-script';\n\n/**\n * Profile lines rendered in the region tooltip, in display order. Each entry maps\n * an `accessBreakdown` group to its Portuguese label. Used to render every line\n * by default, or a single one when `activeProfile` is set.\n */\nconst TOOLTIP_PROFILE_LINES: { key: keyof AccessBreakdown; label: string }[] = [\n { key: 'students', label: 'Estudantes' },\n { key: 'teachers', label: 'Professores(as)' },\n { key: 'managers', label: 'Diretores(as)' },\n];\n\n/**\n * Fade-in animation duration in milliseconds\n */\nconst FADE_DURATION = 400;\n\n/**\n * Hover animation duration in milliseconds\n */\nconst HOVER_DURATION = 200;\n\n/**\n * Target fill opacity for polygons\n */\nconst TARGET_OPACITY = 0.8;\n\n/**\n * Read a CSS custom property value from the document root\n * @param name - CSS variable name (e.g. '--color-map-highlight')\n * @param fallback - Fallback value when running outside browser\n * @returns Resolved CSS variable value\n */\nconst getCssVar = (name: string, fallback = ''): string => {\n if (typeof document === 'undefined') return fallback;\n return (\n getComputedStyle(document.documentElement).getPropertyValue(name).trim() ||\n fallback\n );\n};\n\n/**\n * Build color classes from CSS variables\n * @returns Color class array for the choropleth map\n */\nconst getColorClasses = (): ColorClass[] => [\n {\n min: 0.75,\n max: 1.01,\n fillColor: getCssVar('--color-map-highlight', '#66b584'),\n strokeColor: getCssVar('--color-map-highlight', '#66b584'),\n label: 'Destaque (75% com acesso)',\n },\n {\n min: 0.5,\n max: 0.75,\n fillColor: getCssVar('--color-map-above-avg', '#f8cc2e'),\n strokeColor: getCssVar('--color-map-above-avg', '#f8cc2e'),\n label: 'Acima da média (50 até 74% com acesso)',\n },\n {\n min: 0.25,\n max: 0.5,\n fillColor: getCssVar('--color-map-below-avg', '#fb954b'),\n strokeColor: getCssVar('--color-map-below-avg', '#fb954b'),\n label: 'Abaixo da média (25 até 49% com acesso)',\n },\n {\n min: 0.01,\n max: 0.25,\n fillColor: getCssVar('--color-map-attention', '#b91c1c'),\n strokeColor: getCssVar('--color-map-attention', '#b91c1c'),\n label: 'Ponto de atenção (Abaixo de 25% com acesso)',\n },\n {\n min: -0.01,\n max: 0.01,\n fillColor: getCssVar('--color-map-no-access', '#2f2f2f'),\n strokeColor: getCssVar('--color-map-no-access', '#2f2f2f'),\n label: 'Sem acesso (0%)',\n },\n];\n\n/**\n * Get color class based on value\n * @param value - Normalized value between 0 and 1\n * @param colorClasses - Array of color class configurations\n * @returns Color class configuration\n */\nconst getColorClass = (\n value: number,\n colorClasses: ColorClass[]\n): ColorClass => {\n for (const colorClass of colorClasses) {\n if (value >= colorClass.min && value < colorClass.max) {\n return colorClass;\n }\n }\n return colorClasses[0];\n};\n\n/**\n * Compute NRE boundary polygons by merging city polygons that share the same\n * group (the NRE). Grouping uses `groupName` (the NRE label); when a region has\n * no `groupName`, it falls back to its own `name` so it forms its own group.\n * @param data - Array of region data with individual city GeoJSON features\n * @returns Array of GeoJSON features representing NRE boundaries\n */\nconst computeNREBoundaries = (\n data: RegionData[]\n): Feature<Polygon | MultiPolygon>[] => {\n const groups = new Map<string, RegionData[]>();\n data.forEach((region) => {\n const groupKey = region.groupName ?? region.name;\n const existing = groups.get(groupKey) ?? [];\n existing.push(region);\n groups.set(groupKey, existing);\n });\n\n const boundaries: Feature<Polygon | MultiPolygon>[] = [];\n groups.forEach((regions) => {\n let merged: Feature<Polygon | MultiPolygon> | null = null;\n for (const region of regions) {\n if (region.geoJson.type !== 'Feature') continue;\n const feature: Feature<Polygon | MultiPolygon> = region.geoJson;\n if (merged) {\n const result: Feature<Polygon | MultiPolygon> | null = union({\n type: 'FeatureCollection' as const,\n features: [merged, feature],\n });\n if (result) merged = result;\n } else {\n merged = feature;\n }\n }\n if (merged) boundaries.push(merged);\n });\n\n return boundaries;\n};\n\n/**\n * Create style function for Data Layer features\n *\n * Regions flagged as not managed by the logged user ignore the color\n * classes and are rendered with the unmanaged gray fill.\n *\n * @param opacity - Current fill opacity\n * @param colorClasses - Array of color class configurations\n * @param strokeCityColor - Stroke color for city borders\n * @param unmanagedFillColor - Fill color for regions outside the user scope\n * @returns Style function for map.data.setStyle\n */\nconst createStyleFunction = (\n opacity: number,\n colorClasses: ColorClass[],\n strokeCityColor: string,\n unmanagedFillColor: string\n) => {\n return (feature: google.maps.Data.Feature) => {\n if (feature.getProperty('regionIsManaged') === false) {\n return {\n fillColor: unmanagedFillColor,\n fillOpacity: opacity,\n strokeColor: strokeCityColor,\n strokeWeight: 0.3,\n cursor: 'default',\n };\n }\n const value = feature.getProperty('regionValue') as number;\n const colorClass = getColorClass(value ?? 0, colorClasses);\n return {\n fillColor: colorClass.fillColor,\n fillOpacity: opacity,\n strokeColor: strokeCityColor,\n strokeWeight: 0.3,\n cursor: 'pointer',\n };\n };\n};\n\n/**\n * Map container style\n * Note: Google Maps requires explicit pixel dimensions to render properly\n */\nconst containerStyle = {\n width: '100%',\n height: '415px',\n borderRadius: '0.5rem',\n};\n\n/**\n * Default map center (Paraná, Brazil)\n */\nconst defaultCenter = {\n lat: -24.7,\n lng: -51.5,\n};\n\n/**\n * Legend Item component for the map\n * @param color - Fill color of the legend circle\n * @param label - Text label for the legend item\n * @param borderColor - Optional border color for the legend circle\n * @param active - Whether the legend class is active (visible on map)\n * @param onClick - Callback when the legend item is clicked\n */\nconst LegendItem = ({\n color,\n label,\n borderColor,\n active = true,\n onClick,\n}: {\n color: string;\n label: string;\n borderColor?: string;\n active?: boolean;\n onClick?: () => void;\n}) => (\n <Text\n as=\"button\"\n type=\"button\"\n aria-pressed={active}\n className=\"flex items-center gap-2 cursor-pointer transition-opacity duration-200\"\n style={{ opacity: active ? 1 : 0.4 }}\n onClick={onClick}\n >\n <div\n className=\"w-3 h-3 rounded-full\"\n style={{\n backgroundColor: color,\n border: borderColor ? `1px solid ${borderColor}` : 'none',\n }}\n />\n <Text as=\"span\" size=\"sm\" weight=\"medium\" color=\"text-text-600\">\n {label}\n </Text>\n </Text>\n);\n\n/**\n * Loading skeleton component\n */\nconst LoadingSkeleton = () => (\n <div className=\"w-full h-full flex items-center justify-center bg-background-50 rounded-lg animate-pulse\">\n <Text size=\"sm\" color=\"text-text-400\">\n Carregando mapa...\n </Text>\n </div>\n);\n\n/**\n * ChoroplethMap component for displaying regional performance data\n *\n * Displays an interactive Google Map with colored regions based on normalized values.\n * Uses 4 color classes to represent different performance levels.\n * Includes fade-in animation, smooth hover transitions, and zoom-to-region on click.\n * NRE boundaries are rendered as a separate overlay with thicker strokes.\n *\n * @param data - Array of region data with GeoJSON and values\n * @param apiKey - Google Maps API key\n * @param title - Optional title for the map section\n * @param loading - Loading state indicator\n * @param bounds - Map bounds for initial view\n * @param onRegionClick - Callback when a region is clicked\n * @param className - Additional CSS classes\n * @returns Choropleth map component\n *\n * @example\n * ```tsx\n * <ChoroplethMap\n * data={regionData}\n * apiKey=\"your-api-key\"\n * title=\"Performance por região\"\n * loading={false}\n * onRegionClick={(region) => console.log(region)}\n * />\n * ```\n */\nconst ChoroplethMap = ({\n data,\n apiKey,\n title = 'Performance por região',\n countLabel = 'Acessos',\n loading = false,\n bounds,\n onRegionClick,\n headerAction,\n infoText,\n activeProfile,\n className,\n}: ChoroplethMapProps) => {\n const mapId = GOOGLE_MAPS_LOADER_ID;\n const [map, setMap] = useState<google.maps.Map | null>(null);\n const [hoveredRegion, setHoveredRegion] = useState<RegionData | null>(null);\n const [infoPosition, setInfoPosition] = useState<{\n x: number;\n y: number;\n } | null>(null);\n const [activeClasses, setActiveClasses] = useState<Set<number>>(\n new Set([0, 1, 2, 3, 4])\n );\n const fadeAnimationRef = useRef<number | null>(null);\n const hoverAnimationsRef = useRef<Map<string, number>>(new Map());\n const nreBoundaryLayerRef = useRef<google.maps.Data | null>(null);\n const onRegionClickRef = useRef(onRegionClick);\n onRegionClickRef.current = onRegionClick;\n\n const { isDark } = useTheme();\n\n const dataSignature = useMemo(\n () =>\n data\n .map((d) => {\n const b = d.accessBreakdown;\n const breakdown = b\n ? `${b.students.withAccess}/${b.students.withoutAccess}/${b.teachers.withAccess}/${b.teachers.withoutAccess}/${b.managers.withAccess}/${b.managers.withoutAccess}`\n : '';\n return `${d.id}:${d.value}:${d.name}:${d.groupName ?? ''}:${d.accessCount}:${\n d.isManagedRegion === false ? 0 : 1\n }:${breakdown}`;\n })\n .join('|'),\n [data]\n );\n const stableData = useMemo(() => data, [dataSignature]);\n\n const colorClasses = useMemo(() => getColorClasses(), [isDark]);\n\n const mapOptions: google.maps.MapOptions = useMemo(() => {\n const bgColor = getCssVar('--color-background-50', '#F6F6F6');\n return {\n disableDefaultUI: true,\n zoomControl: true,\n scrollwheel: true,\n draggable: true,\n backgroundColor: bgColor,\n styles: [\n {\n stylers: [{ color: bgColor }],\n },\n {\n elementType: 'labels',\n stylers: [{ visibility: 'off' }],\n },\n {\n elementType: 'geometry.stroke',\n stylers: [{ visibility: 'off' }],\n },\n ],\n };\n }, [isDark]);\n\n const { isLoaded, loadError } = useJsApiLoader({\n id: mapId,\n googleMapsApiKey: apiKey,\n });\n\n /**\n * Handle map load event\n */\n const onLoad = useCallback(\n (mapInstance: google.maps.Map) => {\n setMap(mapInstance);\n\n // Fit bounds if provided so the whole region is framed on load.\n // fitBounds snaps the zoom down to the next level that fits, often\n // leaving the region small in the container. After the fit settles,\n // grow the (fractional) zoom by exactly how much slack is left, so the\n // region fills the container without cropping its edges.\n if (bounds) {\n const googleBounds = new google.maps.LatLngBounds(\n { lat: bounds.south, lng: bounds.west },\n { lat: bounds.north, lng: bounds.east }\n );\n mapInstance.fitBounds(googleBounds, 20);\n google.maps.event.addListenerOnce(mapInstance, 'idle', () => {\n const view = mapInstance.getBounds();\n const zoom = mapInstance.getZoom();\n if (!view || zoom == null) return;\n\n const viewSpanLat =\n view.getNorthEast().lat() - view.getSouthWest().lat();\n const viewSpanLng =\n view.getNorthEast().lng() - view.getSouthWest().lng();\n const targetSpanLat = bounds.north - bounds.south;\n const targetSpanLng = bounds.east - bounds.west;\n if (targetSpanLat <= 0 || targetSpanLng <= 0) return;\n\n // Largest extra zoom that keeps both spans inside the viewport,\n // with a small safety margin for Mercator distortion\n const extraZoom =\n Math.min(\n Math.log2(viewSpanLat / targetSpanLat),\n Math.log2(viewSpanLng / targetSpanLng)\n ) - 0.05;\n\n if (extraZoom > 0) {\n mapInstance.setZoom(zoom + Math.min(extraZoom, 1));\n }\n });\n }\n },\n [bounds]\n );\n\n /**\n * Handle map unmount\n */\n const onUnmount = useCallback(() => {\n if (nreBoundaryLayerRef.current) {\n nreBoundaryLayerRef.current.setMap(null);\n }\n setMap(null);\n }, []);\n\n const nreBoundaries = useMemo(\n () => computeNREBoundaries(stableData),\n [stableData]\n );\n\n /**\n * Add GeoJSON data to map with animations\n */\n useEffect(() => {\n if (!map || !stableData.length) return;\n\n const strokeCityColor = getCssVar('--color-map-stroke-city', '#ffffff');\n const unmanagedFillColor = getCssVar(\n '--color-map-unmanaged-region',\n '#e0e0e0'\n );\n const strokeNreColor = getCssVar('--color-map-stroke-nre', '#ffffff');\n\n // Clear existing data\n map.data.forEach((feature) => {\n map.data.remove(feature);\n });\n\n // Clear existing NRE boundary layer\n if (nreBoundaryLayerRef.current) {\n nreBoundaryLayerRef.current.setMap(null);\n nreBoundaryLayerRef.current = null;\n }\n\n // Add each region's GeoJSON\n stableData.forEach((region) => {\n try {\n const feature = map.data.addGeoJson(region.geoJson);\n // Store region data in the feature\n if (feature && feature.length > 0) {\n feature.forEach((f) => {\n f.setProperty('regionId', region.id);\n f.setProperty('regionName', region.name);\n f.setProperty('regionValue', region.value);\n f.setProperty('regionAccessCount', region.accessCount);\n f.setProperty('regionIsManaged', region.isManagedRegion !== false);\n });\n }\n } catch (error) {\n console.error(\n `Failed to add GeoJSON for region ${region.name}:`,\n error\n );\n }\n });\n\n // Build per-city feature index for O(1) lookup by region id.\n // Hover highlight and click-zoom operate per municipality (not per NRE).\n const cityFeatureIndex = new Map<string, google.maps.Data.Feature[]>();\n map.data.forEach((f) => {\n const id = f.getProperty('regionId') as string;\n if (id) {\n const list = cityFeatureIndex.get(id) ?? [];\n list.push(f);\n cityFeatureIndex.set(id, list);\n }\n });\n\n // Start with opacity 0 for fade-in animation\n map.data.setStyle(\n createStyleFunction(0, colorClasses, strokeCityColor, unmanagedFillColor)\n );\n\n // Add NRE boundary overlay\n const nreLayer = new google.maps.Data();\n nreBoundaries.forEach((boundary) => {\n nreLayer.addGeoJson(boundary);\n });\n nreLayer.setStyle({\n fillOpacity: 0,\n strokeColor: strokeNreColor,\n strokeWeight: 1,\n clickable: false,\n });\n nreLayer.setMap(map);\n nreBoundaryLayerRef.current = nreLayer;\n\n // Animate fade-in from 0 to TARGET_OPACITY\n const startTime = performance.now();\n const animateFadeIn = (currentTime: number) => {\n const elapsed = currentTime - startTime;\n const progress = Math.min(elapsed / FADE_DURATION, 1);\n const currentOpacity = progress * TARGET_OPACITY;\n\n map.data.setStyle(\n createStyleFunction(\n currentOpacity,\n colorClasses,\n strokeCityColor,\n unmanagedFillColor\n )\n );\n\n if (progress < 1) {\n fadeAnimationRef.current = requestAnimationFrame(animateFadeIn);\n } else {\n fadeAnimationRef.current = null;\n }\n };\n fadeAnimationRef.current = requestAnimationFrame(animateFadeIn);\n\n /**\n * Apply style overrides to a list of features\n * @param features - Features to style\n * @param opacity - Fill opacity\n * @param weight - Stroke weight\n */\n const applyHoverStyle = (\n features: google.maps.Data.Feature[],\n opacity: number,\n weight: number\n ) => {\n features.forEach((f) => {\n map.data.overrideStyle(f, {\n fillOpacity: opacity,\n strokeColor: strokeCityColor,\n strokeWeight: weight,\n });\n });\n };\n\n /**\n * Animate hover transition for a group of features.\n * Animations are tracked per NRE group so a new animation only cancels\n * a running one for the SAME group — revert and highlight of different\n * groups must run concurrently.\n * @param groupKey - NRE group name identifying this animation\n * @param features - Target features to animate\n * @param from - Starting opacity\n * @param to - Target opacity\n * @param fromWeight - Starting stroke weight\n * @param toWeight - Target stroke weight\n */\n const animateHover = (\n groupKey: string,\n features: google.maps.Data.Feature[],\n from: number,\n to: number,\n fromWeight: number,\n toWeight: number\n ) => {\n const running = hoverAnimationsRef.current.get(groupKey);\n if (running) {\n cancelAnimationFrame(running);\n }\n const start = performance.now();\n const animate = (now: number) => {\n const progress = Math.min((now - start) / HOVER_DURATION, 1);\n const opacity = from + (to - from) * progress;\n const weight = fromWeight + (toWeight - fromWeight) * progress;\n applyHoverStyle(features, opacity, weight);\n if (progress < 1) {\n hoverAnimationsRef.current.set(\n groupKey,\n requestAnimationFrame(animate)\n );\n } else {\n hoverAnimationsRef.current.delete(groupKey);\n }\n };\n hoverAnimationsRef.current.set(groupKey, requestAnimationFrame(animate));\n };\n\n // Handle hover events - highlight only the hovered city\n let currentCityId: string | null = null;\n let revertTimeout: ReturnType<typeof setTimeout> | null = null;\n\n /**\n * Collect all features belonging to a given city id (O(1) index lookup)\n * @param cityId - Region (city) id to match\n * @returns Array of matching features\n */\n const collectCityFeatures = (\n cityId: string\n ): google.maps.Data.Feature[] => {\n return cityFeatureIndex.get(cityId) ?? [];\n };\n\n const mouseoverListener = map.data.addListener(\n 'mouseover',\n (event: google.maps.Data.MouseEvent) => {\n // Cancel pending revert (when moving between adjacent cities)\n if (revertTimeout) {\n clearTimeout(revertTimeout);\n revertTimeout = null;\n }\n\n const regionId = event.feature.getProperty('regionId') as string;\n const region = stableData.find((r) => r.id === regionId);\n if (region) {\n setHoveredRegion(region);\n if (event.domEvent) {\n setInfoPosition({\n x: (event.domEvent as MouseEvent).clientX,\n y: (event.domEvent as MouseEvent).clientY,\n });\n }\n }\n\n // Animate highlight for the hovered city only\n if (currentCityId !== regionId) {\n if (currentCityId) {\n const prevFeatures = collectCityFeatures(currentCityId);\n animateHover(\n currentCityId,\n prevFeatures,\n 1,\n TARGET_OPACITY,\n 1,\n 0.5\n );\n }\n currentCityId = regionId;\n const cityFeatures = collectCityFeatures(regionId);\n animateHover(regionId, cityFeatures, TARGET_OPACITY, 1, 0.5, 1);\n }\n }\n );\n\n const mouseoutListener = map.data.addListener('mouseout', () => {\n // Defer revert to avoid flicker when moving between adjacent cities\n revertTimeout = setTimeout(() => {\n setHoveredRegion(null);\n setInfoPosition(null);\n if (currentCityId) {\n const prevFeatures = collectCityFeatures(currentCityId);\n animateHover(currentCityId, prevFeatures, 1, TARGET_OPACITY, 1, 0.5);\n currentCityId = null;\n }\n }, 50);\n });\n\n const mousemoveListener = map.data.addListener(\n 'mousemove',\n (event: google.maps.Data.MouseEvent) => {\n if (event.domEvent) {\n setInfoPosition({\n x: (event.domEvent as MouseEvent).clientX,\n y: (event.domEvent as MouseEvent).clientY,\n });\n }\n }\n );\n\n /**\n * Compute bounds for all features matching a given city id\n * @param cityId - Region (city) id to match\n * @returns LatLngBounds encompassing all matching features\n */\n const computeCityBounds = (cityId: string): google.maps.LatLngBounds => {\n const cityBounds = new google.maps.LatLngBounds();\n for (const f of collectCityFeatures(cityId)) {\n f.getGeometry()?.forEachLatLng((latLng: google.maps.LatLng) => {\n cityBounds.extend(latLng);\n });\n }\n return cityBounds;\n };\n\n // Handle click events - zoom to the clicked city\n const clickListener = map.data.addListener(\n 'click',\n (event: google.maps.Data.MouseEvent) => {\n const regionId = event.feature.getProperty('regionId') as string;\n const region = stableData.find((r) => r.id === regionId);\n if (region && region.isManagedRegion !== false) {\n onRegionClickRef.current?.(region);\n }\n\n const cityBounds = computeCityBounds(regionId);\n if (!cityBounds.isEmpty()) {\n map.fitBounds(cityBounds, 20);\n }\n }\n );\n\n return () => {\n if (fadeAnimationRef.current)\n cancelAnimationFrame(fadeAnimationRef.current);\n hoverAnimationsRef.current.forEach((id) => cancelAnimationFrame(id));\n hoverAnimationsRef.current.clear();\n if (revertTimeout) clearTimeout(revertTimeout);\n if (nreBoundaryLayerRef.current) {\n nreBoundaryLayerRef.current.setMap(null);\n nreBoundaryLayerRef.current = null;\n }\n google.maps.event.removeListener(mouseoverListener);\n google.maps.event.removeListener(mouseoutListener);\n google.maps.event.removeListener(mousemoveListener);\n google.maps.event.removeListener(clickListener);\n };\n }, [map, stableData, colorClasses, nreBoundaries]);\n\n /**\n * Apply visibility filter based on active legend classes\n * and adjust map bounds to fit visible features.\n *\n * Bounds are only refit while a legend filter is active: with every class\n * enabled the initial framing comes from the `bounds` prop (whole state),\n * which keeps unmanaged regions in view for region-scoped managers.\n */\n useEffect(() => {\n if (!map || !stableData.length) return;\n\n const isFiltering = activeClasses.size < colorClasses.length;\n const visibleBounds = new google.maps.LatLngBounds();\n let hasVisibleFeatures = false;\n\n map.data.forEach((feature: google.maps.Data.Feature) => {\n // Unmanaged regions don't belong to any legend class: always visible,\n // and they never drive the fit-bounds of the visible selection.\n if (feature.getProperty('regionIsManaged') === false) {\n map.data.overrideStyle(feature, { visible: true });\n return;\n }\n\n const value = feature.getProperty('regionValue') as number;\n const colorClass = getColorClass(value ?? 0, colorClasses);\n const classIndex = colorClasses.indexOf(colorClass);\n\n if (activeClasses.has(classIndex)) {\n map.data.overrideStyle(feature, { visible: true });\n feature.getGeometry()?.forEachLatLng((latLng) => {\n visibleBounds.extend(latLng);\n });\n hasVisibleFeatures = true;\n } else {\n map.data.overrideStyle(feature, { visible: false });\n }\n });\n\n if (isFiltering && hasVisibleFeatures && !visibleBounds.isEmpty()) {\n map.fitBounds(visibleBounds, 20);\n }\n }, [map, stableData, activeClasses, colorClasses]);\n\n /**\n * Toggle a color class on/off in the legend filter\n * @param index - Index of the color class in colorClasses\n */\n const toggleClass = (index: number) => {\n setActiveClasses((prev) => {\n const next = new Set(prev);\n if (next.has(index)) {\n next.delete(index);\n } else {\n next.add(index);\n }\n return next;\n });\n };\n\n if (loadError) {\n return (\n <div className=\"p-5 bg-background border border-border-50 rounded-xl\">\n <Text color=\"text-error-700\">Erro ao carregar o mapa</Text>\n </div>\n );\n }\n\n return (\n <div\n className={cn(\n 'p-5 bg-background border border-border-50 rounded-xl flex flex-col gap-4',\n className\n )}\n >\n {/* Header */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-row items-center gap-4\">\n <Text\n as=\"h2\"\n size=\"lg\"\n weight=\"bold\"\n className=\"flex-1 leading-[21px] tracking-[0.2px]\"\n >\n {title}\n </Text>\n {headerAction}\n </div>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-8\">\n {colorClasses.map((colorClass, index) => (\n <LegendItem\n key={colorClass.label}\n color={colorClass.fillColor}\n borderColor={\n colorClass.fillColor === colorClass.strokeColor\n ? undefined\n : colorClass.strokeColor\n }\n label={colorClass.label}\n active={activeClasses.has(index)}\n onClick={() => toggleClass(index)}\n />\n ))}\n </div>\n </div>\n\n {/* Map Container */}\n <div className=\"bg-background-50 rounded-lg h-[415px] relative overflow-hidden\">\n {loading || !isLoaded ? (\n <LoadingSkeleton />\n ) : (\n <GoogleMap\n mapContainerStyle={containerStyle}\n center={defaultCenter}\n zoom={7}\n onLoad={onLoad}\n onUnmount={onUnmount}\n options={mapOptions}\n />\n )}\n\n {/* Tooltip */}\n {hoveredRegion && infoPosition && (\n <div\n className=\"fixed z-50 flex flex-col gap-2 bg-background-900 shadow-hard-shadow-2 rounded px-3 py-1 pointer-events-none\"\n style={{\n left: Math.min(infoPosition.x + 10, window.innerWidth - 460),\n top: Math.min(infoPosition.y + 10, window.innerHeight - 160),\n }}\n >\n <Text size=\"md\" weight=\"semibold\" color=\"text-text-50\">\n {hoveredRegion.name}\n </Text>\n {hoveredRegion.isManagedRegion !== false && (\n <>\n <div className=\"h-px self-stretch bg-border-200\" />\n {hoveredRegion.accessBreakdown ? (\n <div className=\"flex flex-col gap-1\">\n {TOOLTIP_PROFILE_LINES.filter(\n (line) => !activeProfile || line.key === activeProfile\n ).map((line) => {\n const entry = hoveredRegion.accessBreakdown![line.key];\n return (\n <Text key={line.key} size=\"md\" color=\"text-text-50\">\n {line.label}:{' '}\n {entry.withAccess.toLocaleString('pt-BR')} com acesso,{' '}\n {entry.withoutAccess.toLocaleString('pt-BR')} sem\n acessos\n </Text>\n );\n })}\n </div>\n ) : (\n <Text size=\"md\" color=\"text-text-50\">\n {countLabel}:{' '}\n {hoveredRegion.accessCount.toLocaleString('pt-BR')}\n </Text>\n )}\n </>\n )}\n </div>\n )}\n </div>\n\n {/* Info alert below the map, inside the card */}\n {infoText && (\n <Alert\n action=\"info\"\n variant=\"solid\"\n description={infoText}\n className=\"w-full\"\n />\n )}\n </div>\n );\n};\n\nexport default ChoroplethMap;\n"],"mappings":"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","names":[]}