Refinamiento de Funciones y Estética de Mapa

This commit is contained in:
2025-09-20 22:31:11 -03:00
parent 7d2922aaeb
commit 3750d1a56d
6 changed files with 674 additions and 98 deletions

View File

@@ -9,6 +9,7 @@ import { API_BASE_URL, assetBaseUrl } from '../../../../apiService';
import type { ResultadoMapaDto, AmbitoGeography } from '../../../../types/types';
import { MapaProvincial } from './MapaProvincial';
import { CabaLupa } from './CabaLupa';
import { BiZoomIn, BiZoomOut } from "react-icons/bi";
const DEFAULT_MAP_COLOR = '#E0E0E0';
const FADED_BACKGROUND_COLOR = '#F0F0F0';
@@ -17,7 +18,7 @@ const normalizarTexto = (texto: string = '') => texto.trim().toUpperCase().norma
type PointTuple = [number, number];
const PROVINCE_VIEW_CONFIG: Record<string, { center: PointTuple; zoom: number }> = {
"BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5.5 },
"BUENOS AIRES": { center: [-60.5, -37.3], zoom: 5 },
"SANTA CRUZ": { center: [-69.5, -48.8], zoom: 5 },
"CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 },
"CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 },
@@ -31,7 +32,6 @@ const LUPA_SIZE_RATIO = 0.2;
const MIN_LUPA_SIZE_PX = 100;
const MAX_LUPA_SIZE_PX = 180;
interface MapaNacionalProps {
eleccionId: number;
categoriaId: number;
@@ -41,10 +41,19 @@ interface MapaNacionalProps {
provinciaDistritoId: string | null;
onAmbitoSelect: (ambitoId: string, nivel: 'provincia' | 'municipio', nombre: string) => void;
onVolver: () => void;
isMobileView: boolean;
}
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver }: MapaNacionalProps) => {
const [position, setPosition] = useState({ zoom: 1, center: [-65, -40] as PointTuple });
// --- CONFIGURACIONES DEL MAPA ---
const desktopProjectionConfig = { scale: 700, center: [-65, -40] as [number, number] };
const mobileProjectionConfig = { scale: 1100, center: [-64, -41] as [number, number] };
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver, isMobileView }: MapaNacionalProps) => {
const [position, setPosition] = useState({
zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop
center: [-65, -40] as PointTuple
});
const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const lupaRef = useRef<HTMLDivElement | null>(null);
@@ -70,21 +79,33 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
useEffect(() => {
if (nivel === 'pais') {
setPosition({ zoom: 1, center: [-65, -40] });
setPosition({
zoom: isMobileView ? 1.4 : 1.05, // 1.5 para móvil, 1.05 para desktop
center: [-65, -40]
});
// Reseteamos el ref
initialProvincePositionRef.current = null;
} else if (nivel === 'provincia') {
const nombreNormalizado = normalizarTexto(nombreAmbito);
const manualConfig = PROVINCE_VIEW_CONFIG[nombreNormalizado];
let provinceConfig = { zoom: 7, center: [-65, -40] as PointTuple };
if (manualConfig) {
setPosition(manualConfig);
provinceConfig = manualConfig;
} else {
const provinciaGeo = geoDataNacional.objects.provincias.geometries.find((g: any) => normalizarTexto(g.properties.nombre) === nombreNormalizado);
if (provinciaGeo) {
const provinciaFeature = feature(geoDataNacional, provinciaGeo);
const centroid = geoCentroid(provinciaFeature);
setPosition({ zoom: 7, center: centroid as PointTuple });
provinceConfig = { zoom: 7, center: centroid as PointTuple };
}
}
setPosition(provinceConfig);
// --- Guardar el objeto de posición completo en el ref ---
initialProvincePositionRef.current = provinceConfig;
}
}, [nivel, nombreAmbito, geoDataNacional]);
@@ -104,7 +125,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
const calculatedSize = containerRect.width * LUPA_SIZE_RATIO;
const newLupaSize = Math.max(MIN_LUPA_SIZE_PX, Math.min(calculatedSize, MAX_LUPA_SIZE_PX));
const horizontalOffset = newLupaSize * 0.5;
const verticalOffset = newLupaSize * 0.2;
@@ -132,7 +153,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
let timerId: NodeJS.Timeout;
if (initialLoadRef.current && nivel === 'pais') {
@@ -159,20 +180,93 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
};
}, [position, nivel]);
// --- HANDLERS PARA EL ZOOM ---
const handleZoomIn = () => {
setPosition(prev => ({
...prev,
zoom: Math.min(prev.zoom * 1.8, 100) // Multiplica el zoom actual, con un límite
}));
};
// --- Lógica de reseteo en handleZoomOut ---
const handleZoomOut = () => {
setPosition(prev => {
const newZoom = Math.max(prev.zoom / 1.8, 1);
const initialPos = initialProvincePositionRef.current;
// Si estamos en una provincia Y el nuevo zoom es igual o menor que el inicial...
if (initialPos && newZoom <= initialPos.zoom) {
// ...reseteamos a la posición inicial guardada (zoom Y centro).
return initialPos;
}
// Si no, solo actualizamos el zoom.
return { ...prev, zoom: newZoom };
});
};
const handleMoveEnd = (newPosition: { coordinates: PointTuple, zoom: number }) => {
// Solo actualizamos el centro (coordenadas), no el zoom, al arrastrar
setPosition(prev => ({ ...prev, center: newPosition.coordinates }));
};
const panEnabled =
//isMobileView &&
nivel === 'provincia' &&
initialProvincePositionRef.current !== null &&
position.zoom > initialProvincePositionRef.current.zoom &&
!nombreMunicipioSeleccionado;
const showZoomControls = nivel === 'provincia';
// --- FUNCIÓN DE FILTRO ---
const filterInteractionEvents = (event: any) => {
// La librería pasa un objeto de evento que contiene el evento original del navegador.
// Si el evento original es de la rueda del ratón ('wheel'), siempre lo bloqueamos.
if (event.sourceEvent && event.sourceEvent.type === 'wheel') {
return false;
}
// Para cualquier otro evento (arrastre, etc.), la decisión depende de nuestra lógica `panEnabled`.
return panEnabled;
};
// --- LÓGICA PARA DESHABILITAR EL BOTÓN ---
const isZoomOutDisabled =
(nivel === 'provincia' && initialProvincePositionRef.current && position.zoom <= initialProvincePositionRef.current.zoom) ||
(nivel === 'pais' && position.zoom <= (isMobileView ? 1.4 : 1.05));
return (
<div className="mapa-componente-container" ref={containerRef}>
{showZoomControls && (
<div className="zoom-controls-container">
<button onClick={handleZoomIn} className="zoom-btn" title="Acercar">
<span className="zoom-icon-wrapper"><BiZoomIn /></span>
</button>
<button
onClick={handleZoomOut}
className={`zoom-btn ${isZoomOutDisabled ? 'disabled' : ''}`}
title="Alejar"
disabled={isZoomOutDisabled}
>
<span className="zoom-icon-wrapper"><BiZoomOut /></span>
</button>
</div>
)}
{nivel !== 'pais' && <button onClick={onVolver} className="mapa-volver-btn"> Volver</button>}
<div className="mapa-render-area">
<ComposableMap
projection="geoMercator"
projectionConfig={{ scale: 700, center: [-65, -40] }}
projectionConfig={isMobileView ? mobileProjectionConfig : desktopProjectionConfig}
style={{ width: "100%", height: "100%" }}
>
<ZoomableGroup
center={position.center}
zoom={position.zoom}
filterZoomEvent={() => false}
onMoveEnd={handleMoveEnd}
filterZoomEvent={filterInteractionEvents}
>
<Geographies geography={geoDataNacional}>
{({ geographies }: { geographies: AmbitoGeography[] }) => geographies.map((geo) => {
@@ -233,7 +327,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
</div>
)}
<Tooltip id="mapa-tooltip" />
<Tooltip id="mapa-tooltip" key={nivel} />
</div>
);
};