Feat Widgets Controles y Estilos

This commit is contained in:
2025-10-03 13:26:20 -03:00
parent 1719e79723
commit 64d45a7a39
17 changed files with 544 additions and 278 deletions

View File

@@ -1,4 +1,3 @@
// src/features/legislativas/nacionales/components/MapaNacional.tsx
import axios from 'axios';
import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
import { useSuspenseQuery } from '@tanstack/react-query';
@@ -12,6 +11,7 @@ import { MapaProvincial } from './MapaProvincial';
import { CabaLupa } from './CabaLupa';
import { BiZoomIn, BiZoomOut } from "react-icons/bi";
import toast from 'react-hot-toast';
import { useMediaQuery } from '../hooks/useMediaQuery';
const DEFAULT_MAP_COLOR = '#E0E0E0';
const FADED_BACKGROUND_COLOR = '#F0F0F0';
@@ -19,15 +19,21 @@ 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 },
"SANTA CRUZ": { center: [-69.5, -49.3], zoom: 5 },
"CIUDAD AUTONOMA DE BUENOS AIRES": { center: [-58.45, -34.6], zoom: 85 },
"CHUBUT": { center: [-68.5, -44.5], zoom: 5.5 },
"SANTA FE": { center: [-61, -31.2], zoom: 6 },
"CORRIENTES": { center: [-58, -29], zoom: 7 },
"RIO NEGRO": { center: [-67.5, -40], zoom: 5.5 },
"TIERRA DEL FUEGO": { center: [-66.5, -54.2], zoom: 7 },
interface ViewConfig {
center: PointTuple;
zoom: number;
}
const PROVINCE_VIEW_CONFIG: Record<string, { desktop: ViewConfig; mobile?: ViewConfig }> = {
"BUENOS AIRES": { desktop: { center: [-60.5, -37.3], zoom: 5 }, mobile: { center: [-60, -38], zoom: 5.5 } },
"SANTA CRUZ": { desktop: { center: [-69.5, -49.3], zoom: 5 }, mobile: { center: [-69.5, -50], zoom: 4 } },
"CIUDAD AUTONOMA DE BUENOS AIRES": { desktop: { center: [-58.44, -34.65], zoom: 150 } },
"CHUBUT": { desktop: { center: [-68.5, -44.5], zoom: 5.5 }, mobile: { center: [-68, -44.5], zoom: 4.5 } },
"SANTA FE": { desktop: { center: [-61, -31.2], zoom: 6 }, mobile: { center: [-61, -31.5], zoom: 7.5 } },
"CORRIENTES": { desktop: { center: [-58, -29], zoom: 7 }, mobile: { center: [-57.5, -28.8], zoom: 9 } },
"RIO NEGRO": { desktop: { center: [-67.5, -40], zoom: 5.5 }, mobile: { center: [-67.5, -40], zoom: 4.3 } },
"SALTA": { desktop: { center: [-64.5, -24], zoom: 7 }, mobile: { center: [-65.5, -24.5], zoom: 6 } },
"TIERRA DEL FUEGO": { desktop: { center: [-66.5, -54.2], zoom: 7 }, mobile: { center: [-66, -54], zoom: 7.5 } },
};
const LUPA_SIZE_RATIO = 0.2;
@@ -48,15 +54,20 @@ interface MapaNacionalProps {
// --- CONFIGURACIONES DEL MAPA ---
const desktopProjectionConfig = { scale: 700, center: [-65, -40] as [number, number] };
const mobileProjectionConfig = { scale: 1100, center: [-64, -41] as [number, number] };
const mobileProjectionConfig = { scale: 1100, center: [-64, -42.5] as [number, number] };
// --- LÍNEA A CALIBRAR ---
const mobileSmallProjectionConfig = { scale: 900, center: [-64, -43] as [number, number] };
export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nombreProvinciaActiva, provinciaDistritoId, onAmbitoSelect, onVolver, isMobileView }: MapaNacionalProps) => {
const isMobileSmall = useMediaQuery('(max-width: 380px)');
const [position, setPosition] = useState({
zoom: isMobileView ? 1.5 : 1.05, // 1.5 para móvil, 1.05 para desktop
center: [-65, -40] as PointTuple
zoom: isMobileView ? 1.5 : 1.05,
center: isMobileView ? mobileProjectionConfig.center : desktopProjectionConfig.center as PointTuple
});
const [isPanning, setIsPanning] = useState(false);
const initialProvincePositionRef = useRef<{ zoom: number, center: PointTuple } | null>(null);
const initialProvincePositionRef = useRef<ViewConfig | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const lupaRef = useRef<HTMLDivElement | null>(null);
@@ -82,32 +93,38 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
useEffect(() => {
if (nivel === 'pais') {
const currentMobileConfig = isMobileSmall ? mobileSmallProjectionConfig : mobileProjectionConfig;
const currentMobileZoom = isMobileSmall ? 1.4 : 1.5;
setPosition({
zoom: isMobileView ? 1.4 : 1.05,
center: [-65, -40]
zoom: isMobileView ? currentMobileZoom : 1.05,
center: isMobileView ? currentMobileConfig.center : desktopProjectionConfig.center
});
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 };
let provinceConfig: ViewConfig | undefined;
if (manualConfig) {
provinceConfig = manualConfig;
provinceConfig = (isMobileView && manualConfig.mobile) ? manualConfig.mobile : manualConfig.desktop;
} 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);
provinceConfig = { zoom: 7, center: centroid as PointTuple };
provinceConfig = { zoom: isMobileView ? 8 : 7, center: centroid as PointTuple };
}
}
setPosition(provinceConfig);
initialProvincePositionRef.current = provinceConfig;
if (provinceConfig) {
setPosition(provinceConfig);
initialProvincePositionRef.current = provinceConfig;
}
}
}, [nivel, nombreAmbito, geoDataNacional, isMobileView]);
}, [nivel, nombreAmbito, geoDataNacional, isMobileView, isMobileSmall]);
const resultadosNacionalesPorNombre = new Map<string, ResultadoMapaDto>(mapaDataNacional.map(d => [normalizarTexto(d.ambitoNombre), d]));
const nombreMunicipioSeleccionado = nivel === 'municipio' ? nombreAmbito : null;
@@ -173,14 +190,9 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
position.zoom > initialProvincePositionRef.current.zoom &&
!nombreMunicipioSeleccionado;
// --- INICIO DE LA CORRECCIÓN ---
const handleZoomIn = () => {
// Solo mostramos la notificación si el paneo NO está ya habilitado
if (!panEnabled && initialProvincePositionRef.current) {
// Calculamos cuál será el nuevo nivel de zoom
const newZoom = position.zoom * 1.8;
// Si el nuevo zoom supera el umbral inicial, activamos la notificación
if (newZoom > initialProvincePositionRef.current.zoom) {
toast.success('Desplazamiento Habilitado', {
icon: '🖐️',
@@ -193,10 +205,8 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
};
const handleZoomOut = () => {
// Solo mostramos la notificación si el paneo SÍ está habilitado actualmente
if (panEnabled && initialProvincePositionRef.current) {
const newZoom = position.zoom / 1.8;
// Si el nuevo zoom es igual o menor al umbral, desactivamos
if (newZoom <= initialProvincePositionRef.current.zoom) {
toast.error('Desplazamiento Deshabilitado', {
icon: '🔒',
@@ -205,7 +215,6 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
});
}
}
// La lógica para actualizar la posición no cambia
setPosition(prev => {
const newZoom = Math.max(prev.zoom / 1.8, 1);
const initialPos = initialProvincePositionRef.current;
@@ -254,7 +263,7 @@ export const MapaNacional = ({ eleccionId, categoriaId, nivel, nombreAmbito, nom
<div className="mapa-render-area">
<ComposableMap
projection="geoMercator"
projectionConfig={isMobileView ? mobileProjectionConfig : desktopProjectionConfig}
projectionConfig={isMobileSmall ? mobileSmallProjectionConfig : (isMobileView ? mobileProjectionConfig : desktopProjectionConfig)}
style={{ width: "100%", height: "100%" }}
>
<ZoomableGroup